Tag

slider

Browsing

LiveData is introduced as lifecycle-aware data holder with the observer pattern. That means, When something is new in data set it will notify. It automatically changes the view for the changes in the data set. In this tutorial, we will demonstrate LiveData android example

When we have LiveData object(eg. list of customers, can be a list of users, it can be any data sources ), we add some Lifecycle Owner (such as Activity or Fragment) as an observer to this data update. Same as observer pattern but with respecting lifecycle states.

In large application there can be two type of data source. Local Sqlite database, remote Rest APIs . With Live we can write code for observer these data source for data changes and update views accordingly.

Benefits of using LiveData

  • Ensures your UI matches your data state- It based on observer pattern. So we’ll be notified every time the data changes instead of requesting the data each time from ViewModel
  • Avoid memory leaks -- Observers bounded with life cycle and when lifecycle is destroyed LiveData object also destroy.
  • No more crashes due to stopped activities- If the observer’s lifecycle is inactive, such as in the case of an activity in the back stack, then it doesn’t receive any LiveData events.
  • No more manual lifecycle handling –  UI components just observe relevant data and don’t stop or resume observation. LiveData automatically manages all of this since it’s aware of the relevant lifecycle status changes while observing.
  • Always up to date data -- If a lifecycle becomes inactive, it receives the latest data upon becoming active again. For example, an activity that was in the background receives the latest data right after it returns to the foreground.
  • Manage all configuration changes --If an activity or fragment is recreated due to a configuration change, like device rotation, it immediately receives the latest available data.

Step for implementation LiveData

  • Add dependencies into our app/build.gradle file
  • Create a subclass of AndroidViewModel or ViewModel (as per instance )
  • Declare an of the mutable live data object. That takes input from the repository or other sources.
  • In Activity create an instance of LiveData and subscribes the observer

LiveData Demo App

Now, I will demonstrate how to implement LiveData in Android application. In this LiveData android example, we use Retrofit2 for Rest API calling and will fetch data from a remote server and display it on RecyclerView with the help of ViewModel and LiveData.

The following component we will using this demo application
  • LifeCycle Awareness
  • ViewModel
  • LiveData
  • Retrofit
  • RxJava we are not using because I don’t want complexity this demo
  • Glide
  • Lamda Expression

1. Create a new Project with EmptyActivity Template

In AndroidStudio, create a new project with EmptyActivity template named is LiveDataExample . Now just add dependency into the app/build.gradle .

    def lifecycle_version = "1.1.1"
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"

    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    // Glide for image loading
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'

We also use lambda expression in this project so set compile option 1.8 inside android tag in app/build.gradle.

compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

2. Create Wrapper class for parsing JSON response

I will use retrofit and LiveData for getting data from remote server. The rest API endpoint is this https://androidwave.com/api/feed.json. and JSON data like below

{
  "status": "ok",
  "error":false,
  "message":"AndroidWave RSS feed found",
  "data": [
   {
      "title": "Background Limitations Android Oreo",
      "pubDate": "2019-02-09 11:22:31",
      "link": "https://android.com/background-limitations-android-oreo/",
      "author": "admin",
      "thumbnail": "http://35.197.65.22/wp-content/uploads/2019/02/background-limitations-android-oreo-370x247.png",
      "description": "If you are looking at what exactly limitations are applied in background service in Android in Oreo. You are in right place."
    },
   {
      "title": "ViewModel Android Example",
      "pubDate": "2019-02-09 11:22:31",
      "link": "https://android.com/viewmodel-android-example/",
      "author": "admin",
      "thumbnail": "http://35.197.65.22/wp-content/uploads/2019/02/viewmodel-android-example-370x247.png",
      "description": "When you are developing professional-level android application, one of the most common things we need the consider is configuration changes."
    },
   {
      "title": "Working with JobIntentService",
      "pubDate": "2019-02-09 11:22:31",
      "link": "https://android.com/working-with-jobintentservice/",
      "author": "admin",
      "thumbnail": "http://35.197.65.22/wp-content/uploads/2019/02/working-with-job-intent-service-370x247.png",
      "description": "In our previous article, I have explained the limitation of using the IntentService in Android Oreo devices. To overcome this problem android introduced JobIntentService"
    }]
}
2.1 Just create a wrapper class name is BlogWrapper and set getter setter like below
package com.wave.livedataexample.model;

import com.google.gson.annotations.SerializedName;

import java.util.List;

@SuppressWarnings("unused")
public class BlogWrapper {

    @SerializedName("data")
    private List<Blog> mData;
    @SerializedName("error")
    private Boolean mError;
    @SerializedName("message")
    private String mMessage;
    @SerializedName("status")
    private String mStatus;

    public List<Blog> getBlog() {
        return mData;
    }

    public void setBlog(List<Blog> data) {
        mData = data;
    }

    public Boolean getError() {
        return mError;
    }

    public void setError(Boolean error) {
        mError = error;
    }

    public String getMessage() {
        return mMessage;
    }

    public void setMessage(String message) {
        mMessage = message;
    }

    public String getStatus() {
        return mStatus;
    }

    public void setStatus(String status) {
        mStatus = status;
    }

}
2.2 Now create Wrapper class for Blog Item.
package com.wave.livedataexample.model;

import com.google.gson.annotations.SerializedName;

public class Blog {

    @SerializedName("author")
    private String mAuthor;
    @SerializedName("description")
    private String mDescription;
    @SerializedName("link")
    private String mLink;
    @SerializedName("pubDate")
    private String mPubDate;
    @SerializedName("thumbnail")
    private String mThumbnail;
    @SerializedName("title")
    private String mTitle;

    public String getAuthor() {
        return mAuthor;
    }

    public void setAuthor(String author) {
        mAuthor = author;
    }

    public String getDescription() {
        return mDescription;
    }

    public void setDescription(String description) {
        mDescription = description;
    }

    public String getLink() {
        return mLink;
    }

    public void setLink(String link) {
        mLink = link;
    }

    public String getPubDate() {
        return mPubDate;
    }

    public void setPubDate(String pubDate) {
        mPubDate = pubDate;
    }

    public String getThumbnail() {
        return mThumbnail;
    }

    public void setThumbnail(String thumbnail) {
        mThumbnail = thumbnail;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }

}

3. Create a Repository for interacting to LiveData

For now create a java class named BlogRepository and declare mutable LiveData object like below.

public class BlogRepository {
    private ArrayList<Blog> movies = new ArrayList<>();
    private MutableLiveData<List<Blog>> mutableLiveData = new MutableLiveData<>();
    private Application application;
    
    public BlogRepository(Application application) {
        this.application = application;
    }

}
3.1 For interacting remote create a retrofit interface and expose below methods.

Now setup a retrofit with LiveData for interacting WebService.

package com.wave.livedataexample.service;

import com.wave.livedataexample.model.BlogWrapper;
import retrofit2.Call;
import retrofit2.http.GET;

public interface RestApiService {

    @GET("feed.json")
    Call<BlogWrapper> getPopularBlog();

}
3.2 Furthermore create a retrofit instance like below
package com.wave.livedataexample.service;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import static com.wave.livedataexample.BuildConfig.BASE_URL;

public class RetrofitInstance {

    private static Retrofit retrofit = null;
    public static RestApiService getApiService() {
        if (retrofit == null) {
            retrofit = new Retrofit
                    .Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit.create(RestApiService.class);
    }
}
3.3 Now Open the app/build.gradle and define BASE_URL inside the config/defaultConfig tag
 buildConfigField "String", "BASE_URL", '"https://androidwave.com/api/"'
3.4 Now open the BlogRepository and expose getMutableLiveData()
public MutableLiveData<List<Blog>> getMutableLiveData() {

        RestApiService apiService = RetrofitInstance.getApiService();
        Call<BlogWrapper> call = apiService.getPopularBlog();
        call.enqueue(new Callback<BlogWrapper>() {
            @Override
            public void onResponse(Call<BlogWrapper> call, Response<BlogWrapper> response) {
                BlogWrapper mBlogWrapper = response.body();
                if (mBlogWrapper != null && mBlogWrapper.getBlog() != null) {
                    movies = (ArrayList<Blog>) mBlogWrapper.getBlog();
                    mutableLiveData.setValue(movies);
                }
            }
            @Override
            public void onFailure(Call<BlogWrapper> call, Throwable t) {
            }
        });
        return mutableLiveData;
    }
3.5 The complete BlogRepository class looks like this
package com.wave.livedataexample.model;

import android.app.Application;
import android.arch.lifecycle.MutableLiveData;
import com.wave.livedataexample.service.RestApiService;
import com.wave.livedataexample.service.RetrofitInstance;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class BlogRepository {
    private ArrayList<Blog> movies = new ArrayList<>();
    private MutableLiveData<List<Blog>> mutableLiveData = new MutableLiveData<>();
    private Application application;

    public BlogRepository(Application application) {
        this.application = application;
    }

    public MutableLiveData<List<Blog>> getMutableLiveData() {

        RestApiService apiService = RetrofitInstance.getApiService();
        Call<BlogWrapper> call = apiService.getPopularBlog();
        call.enqueue(new Callback<BlogWrapper>() {
            @Override
            public void onResponse(Call<BlogWrapper> call, Response<BlogWrapper> response) {
                BlogWrapper mBlogWrapper = response.body();
                if (mBlogWrapper != null && mBlogWrapper.getBlog() != null) {
                    movies = (ArrayList<Blog>) mBlogWrapper.getBlog();
                    mutableLiveData.setValue(movies);
                }
            }

            @Override
            public void onFailure(Call<BlogWrapper> call, Throwable t) { }
        });
        return mutableLiveData;
    }
}

4. Create ViewModel

Create a subclass of ViewModel named MainViewModel (Main is taking from the name of an activity, such as for login activity we can use LoginViewModel ). MainViewModel class is extends AndroidViewModel. So basically two superclasses for ViewModel in Android. ViewModel and AndroidViewModel. different is only visibility of Context. AndroidViewModel has an application Context that by we are using here AndroidViewModel.

package com.wave.livedataexample.viewmodel;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.support.annotation.NonNull;

import com.wave.livedataexample.model.Blog;
import com.wave.livedataexample.model.BlogRepository;

import java.util.List;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
public class MainViewModel extends AndroidViewModel {
    private BlogRepository movieRepository;

    public MainViewModel(@NonNull Application application) {
        super(application);
        movieRepository = new BlogRepository(application);
    }

    public LiveData<List<Blog>> getAllBlog() {
        return movieRepository.getMutableLiveData();
    }


}

Now the LiveData and View Model part is ready to use.

5. Create a XML layout for blog item

In this demo we are showing the blog list over the RecyclerView. So now create a layout for blog item named blog_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:background="#fff"
    android:padding="8dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:fontFamily="@font/montserrat"
        android:gravity="left"
        android:includeFontPadding="true"
        android:letterSpacing="-0.02"
        android:lineSpacingExtra="5sp"
        android:text="Dagger2 Android Example"
        android:textAlignment="gravity"
        android:textColor="@color/navy"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/ivThumbnail"
        android:layout_width="0dp"
        android:layout_height="210dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:scaleType="fitXY"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        android:id="@+id/tvDescription"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:fontFamily="@font/montserrat"
        android:text="https://androidwave.com/dagger2-android-example/"
        android:textColor="@color/navy"
        android:textSize="14sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ivThumbnail" />

    <TextView
        android:id="@+id/tvLink"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:fontFamily="@font/montserrat"
        android:text="https://androidwave.com/dagger2-android-example/"
        android:textColor="@color/windows_blue"
        android:textSize="15sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvDescription" />
</android.support.constraint.ConstraintLayout>

6. Create a RecyclerView adapter named BlogAdapter

package com.wave.livedataexample.ui;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.wave.livedataexample.R;
import com.wave.livedataexample.model.Blog;

import java.util.List;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
public class BlogAdapter extends RecyclerView.Adapter<BaseViewHolder> {
    private static final String TAG = "BlogAdapter";

    private List<Blog> mBlogList;

    public BlogAdapter(List<Blog> blogList) {
        mBlogList = blogList;
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        holder.onBind(position);
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(
                LayoutInflater.from(parent.getContext()).inflate(R.layout.blog_item, parent, false));


    }

    @Override
    public int getItemViewType(int position) {
        return 0;
    }

    @Override
    public int getItemCount() {
        if (mBlogList != null && mBlogList.size() > 0) {
            return mBlogList.size();
        } else {
            return 0;
        }
    }

    public class ViewHolder extends BaseViewHolder {

        ImageView ivThumbnail;
        TextView tvTitle;
        TextView tvDescription;
        TextView tvLink;

        public ViewHolder(View itemView) {
            super(itemView);
            ivThumbnail = itemView.findViewById(R.id.ivThumbnail);
            tvTitle = itemView.findViewById(R.id.tvTitle);
            tvDescription = itemView.findViewById(R.id.tvDescription);
            tvLink = itemView.findViewById(R.id.tvLink);

        }

        protected void clear() {
            ivThumbnail.setImageDrawable(null);
            tvTitle.setText("");
            tvLink.setText("");
        }

        public void onBind(int position) {
            super.onBind(position);

            final Blog mBlog = mBlogList.get(position);

            if (mBlog.getThumbnail() != null) {
                Glide.with(itemView.getContext())
                        .load(mBlog.getThumbnail())
                        .into(ivThumbnail);
            }

            if (mBlog.getTitle() != null) {
                tvTitle.setText(mBlog.getTitle());
            }

            if (mBlog.getDescription() != null) {
                tvDescription.setText(mBlog.getDescription());
            }

            if (mBlog.getLink() != null) {
                tvLink.setText(mBlog.getLink());
            }

            tvLink.setOnClickListener(v -> {
                if (mBlog.getLink() != null) {
                    try {
                        Intent intent = new Intent();
                        intent.setAction(Intent.ACTION_VIEW);
                        intent.addCategory(Intent.CATEGORY_BROWSABLE);
                        intent.setData(Uri.parse(mBlog.getLink()));
                        itemView.getContext().startActivity(intent);
                    } catch (Exception e) {
                        Log.e(TAG, "onClick: Image url is not correct");
                    }
                }
            });
        }
    }

}
6.1 BaseViewHolder
package com.wave.livedataexample.ui;

import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
public abstract class BaseViewHolder extends RecyclerView.ViewHolder {

    private int mCurrentPosition;

    public BaseViewHolder(View itemView) {
        super(itemView);
    }

    protected abstract void clear();

    public void onBind(int position) {
        mCurrentPosition = position;
        clear();
    }

    public int getCurrentPosition() {
        return mCurrentPosition;
    }
}

7. Now Open the activity_main.xml and add RecyclerView inside SwipeRefreshLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f2f2f2"
    android:padding="8dp"
    tools:context=".ui.MainActivity">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swiperefresh"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">


        <android.support.v7.widget.RecyclerView
            android:id="@+id/blogRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>

8. Now open the MainActivity and do following operation

  1. initialize views such as SwipeRefreshLayout, RecyclerView with help of findViewById
  2. Create the instance of MainViewModel with help of ViewModelProviders
  3. Applied the getAllBlog() observer on ViewModel that observe List<Blog>>
  4. Finally, prepare BlogAadapter and set on RecyclerView
  5. At last add internet permission in manifest (<uses-permission android:name=”android.permission.INTERNET” />)

Finally, MainActivity source code seems like below

package com.wave.livedataexample.ui;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.wave.livedataexample.R;
import com.wave.livedataexample.model.Blog;
import com.wave.livedataexample.viewmodel.MainViewModel;

import java.util.List;

public class MainActivity extends AppCompatActivity {


    RecyclerView mRecyclerView;
    SwipeRefreshLayout swipeRefresh;
    private MainViewModel mainViewModel;

    BlogAdapter mBlogAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializationViews();
        mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        getPopularBlog();
        // lambda expression
        swipeRefresh.setOnRefreshListener(() -> {
            getPopularBlog();
        });
    }

    private void initializationViews() {
        swipeRefresh = findViewById(R.id.swiperefresh);
        mRecyclerView = findViewById(R.id.blogRecyclerView);
    }

    public void getPopularBlog() {
        swipeRefresh.setRefreshing(true);
        mainViewModel.getAllBlog().observe(this, new Observer<List<Blog>>() {
            @Override
            public void onChanged(@Nullable List<Blog> blogList) {
                swipeRefresh.setRefreshing(false);
                prepareRecyclerView(blogList);
            }
        });
        /**
         * Replace this statement with lambda expression
         * For using set you have to set following lines in app/build.gradle
             // add below line
             compileOptions {
             sourceCompatibility JavaVersion.VERSION_1_8
             targetCompatibility JavaVersion.VERSION_1_8
             }
             // reduce line of code
             mainViewModel.getAllBlog().observe(this, blogList -> prepareRecyclerView(blogList));

         */

    }

    private void prepareRecyclerView(List<Blog> blogList) {

        mBlogAdapter = new BlogAdapter(blogList);
        if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        } else {
            mRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));

        }
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(mBlogAdapter);
        mBlogAdapter.notifyDataSetChanged();

    }

}

After following all the above step, build the project and RUN it. The output will be as expected. If you have any queries, feel free to ask them in the comment section below. Happy Coding 🙂

Download Sample Project- LiveData Android Example

Welcome Guys, In this post I’ll tell you why you should use Kotlin for Android development? Android discuss all advantages of Kotlin over Java in Android for Android Development. Why Kotlin is more powerful than Java

Kotlin

It is a Multi-Platform, concise, expressive, and powerful programming language. Best thing, it’s interoperable with Java Virtual Machine and Android Runtime.

Kotlin is modern safe and concise while being expressive.  Kotlin is adapt and be adaptable to much different domains like –  you can build server application front- end application as well as Android application.

 It contains safety feature for nullability,  Avoid an entire class of null pointer exception. Handle immutable of variable threw out the applications. Kotlin help to make a healthy app and improve performance of the application.

Kotlin is easy to learn for an existing developer, especially for Java developer. The interoperable feature makes more easy to use in an existing Java project.

1. Safer Code

Kotlin contains nullability feature. The Kotlin has eliminated the danger of null references from code throughout the app,  write safe code and avoid NullPointerExceptions exception.

Normally, In Java you declare like this
String userName;
userName = null;
But Kotlin handle this state using !! and ? operator

(!! operator indicates not nullable,? the operator tells the compiler it can be null)

var output: String
output = null   // Compilation error
val name: String? = null    // Nullable type
println(name.length())      // Compilation error

2. Concise

Focus on expressing your ideas and write less boilerplate code you need to write. Create a POJO with equals(), getters, setters, hashCode(), and copy(), toString() with a single line like below.

data class LoginUser(val userName: String, val email: String)

3. Lambda Expression

Kotlin natively supports lambdas expression, that simplifies your code and reduces the number of line of the code in Android. You thinking hows .?? check below example

In Java you write click listener code like below.
button.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v){
        loginUser();
    }
});
In Kotlin you need write single line  code
button.setOnClickListener { loginUser() }
Reduce the number of overloaded functions by using default arguments. Call functions using named arguments to make your code more readable.
fun format(str: String,
           normalizeCase: Boolean = true,
           upperCaseFirstLetter: Boolean = true,
           divideByCamelHumps: Boolean = false,
           wordSeparator: Char = ' ') {
      //....
    }
// You can call Call function with named argument eg.
format(str, normalizeCase = true, upperCaseFirstLetter = true)

4. Forgot about findViewById

In Kotlin, You now need to call findViewById in Kotlin you can access all views component directly using ID.

In Java, you bind view like this way
TextView textHello = (TextView) findViewById(R.id.textView);
textHello.setText("The AndroidWave | Leading App Developer in India !")
How to access component in Kotlin
import kotlinx.android.synthetic.main.content_main.*
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // No need to call findViewById(R.id.textView) as TextView
        textView.text = "The AndroidWave | Leading App Developer in India"
}

5. 101% Interoperable with Java

Interoperable is played an important role in your while you learn a new language. Interoperability means you can use all java code in Kotlin in a single click.  Everything you can do with Java, you can do in Kotlin. Kotlin is a JVM language that’s completely interoperable with Java

Way of calling Java Code in Kotlin
class KotlinClass {
    fun kotlinDoSomething() {
        val javaClass = JavaClass()
        javaClass.javaDoSomething()
        println(JavaClass().prop)
    }
}
Way of Calling Kotlin code from Java
public class JavaClass {
    public String getProp() { return "Hello"; }
    public void javaDoSomething() {
        new KotlinClass().kotlinDoSomething();
    }
}

6. Smart Casting

Below I’m describing uses a feature of Kotlin called Smart Cast. , Let’s compare how we do class casting in Java vs Kotlin.

In Java, We first check the type of the variable using the instanceof operator and after that will cast it to the target type like this 

Object obj = "TThe AndroidWave | Leading App Developer in India";
if(obj instanceof String) {
    // Explicit Casting to `String`
    String str = (String) obj;
    System.out.println("Found a String of length " + str.length());
}

But In Kotlin, When you perform an i is or !is check on a variable, the compiler tracks this information and automatically casts the variable to the target type in the scope where the is or !is check is true.

val obj: Any = "The AndroidWave | Leading App Developer in India"
if(obj is String) {
    // The variable obj is automatically cast to a String in this scope.
    // No Explicit Casting needed. 
    println("Found a String of length ${obj.length}")
}

7. Default Arguments

class Developer(val name: String,
 val age: Int,
 val someValue: Int = 0,
 val profile: String = “”) {
}
 
val amit = Developer(“Amit Shekhar”, 22, 10, “Android Developer”)
val anand = Developer(“Anand Gaurav”, 20, 11)
val ravi = Developer(“Ravi Kumar”, 26)

8. Great Tooling Support

Android Studio 3.0 provides helpful tools to help you start using Kotlin. Convert entire Java files or convert code snippets on the fly when you paste Java code into a Kotlin file.

We demonstrate video streaming from the server using the ExoPlayer. In fact, youtube player also used ExoPlayer for streaming video. This ExoPlayer android example I will explain how to use ExoPlayer in own android application.

ExoPlayer is an open-source project. This is not a part of Android SDK. In Android, ExoPlayer is application level media player. It’s standard audio and video components are built on Android’s MediaCodec API, which was released in Android.

ExoPlayer is easy to use, maintainable and fully customize.

ExoPlayer Android Tutorial Sample App

1. Create a new project.

Go to File Menu and Create a New Project, fill information like project and package name after that click Next and select target SDK and Finish

2. After that add below dependency in build.gradle.

    // add exo player dependency here
    implementation 'com.google.android.exoplayer:exoplayer:2.7.3'
    implementation 'org.jsoup:jsoup:1.10.3'

    // code generator for view
    implementation "com.jakewharton:butterknife:8.8.1"
    annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1"

In the gradle.xml file, We have 2 major dependencies. One for ExoPlayer and second as know about ButterKnife already. Basically, the butterknife is injected of view in the java file.

3. Open activity_main.xml and add two Button like below code.

While creating project Android Studio is provide default template like EmptyActivity. So MainActivity.java and activity_main.xml is automatically created. So open activity_main.xml and add to button

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_design"
    tools:context=".MainActivity"
    >

  <Button
      android:id="@+id/buttonPlayUrlVideo"
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="16dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:background="@color/colorAccent"
      android:text="@string/title_play_url"
      android:textColor="@color/white"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0.5"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/buttonPlayDefaultVideo"
      />

  <Button
      android:id="@+id/buttonPlayDefaultVideo"
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:background="@color/colorAccent"
      android:text="@string/title_play_default_url"
      android:textColor="@color/white"
      app:layout_constraintBottom_toTopOf="@+id/buttonPlayUrlVideo"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0.5"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintVertical_chainStyle="packed"
      />
</android.support.constraint.ConstraintLayout>

As per design, we have created two buttons one for play default video. The second user can put own URL in dialog prompt.

4.1 Now connect this layout with MainActivity.java

Open the main activity, we generate view inject code by using ButterKnief injection and set on click listeners.

  @BindView(R.id.buttonPlayUrlVideo)
  Button buttonPlayUrlVideo;
  @BindView(R.id.buttonPlayDefaultVideo)
  Button buttonPlayDefaultVideo;

  @OnClick({ R.id.buttonPlayUrlVideo, R.id.buttonPlayDefaultVideo })
  public void onViewClicked(View view) {
    switch (view.getId()) {
      case R.id.buttonPlayUrlVideo:
        showDialogPrompt();
        break;
      case R.id.buttonPlayDefaultVideo:
        Intent mIntent =
            ExoPlayerActivity.getStartIntent(this, VideoPlayerConfig.DEFAULT_VIDEO_URL);
        startActivity(mIntent);
        break;
    }
  }
4.2. Let’s bind butter knife in onCreate Activity
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    getSupportActionBar().hide();
    ButterKnife.bind(this);
  }

4.3. -As per above, We are using two buttons. So If you want to play a default video then we call intent with default URL of ExoPlayer activity.
like Below

  // End Activity Here
  Intent mIntent = ExoPlayerActivity.getStartIntent(this, VideoPlayerConfig.DEFAULT_VIDEO_URL);

  startActivity(mIntent);


4.3. – Button for playing its own video by entering video URL. So taking user URL we have created alert dialog with edit text.

Just create a layout file for dialog view. In res, folder create file named dialog_prompts.xml and add few components

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/default_item_padding"
    >

  <TextView
      android:id="@+id/textView1"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:text="Type Your Url"
      android:textAppearance="?android:attr/textAppearanceLarge"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

  <EditText
      android:id="@+id/editTextDialogUrlInput"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/textView1"
      >

    <requestFocus />

  </EditText>

</android.support.constraint.ConstraintLayout>

Open again MainActivity.java and put this code for creating dialog

  private void showDialogPrompt() {
    // get dialog_prompts.xml view
    LayoutInflater li = LayoutInflater.from(this);
    View promptsView = li.inflate(R.layout.dialog_prompts, null);
    AlertDialog.Builder mBuilder = new AlertDialog.Builder( this);
    // set dialog_prompts.xml to dialog
    mBuilder.setView(promptsView);
    final EditText userInputURL = (EditText) promptsView
        .findViewById(R.id.editTextDialogUrlInput);
    // set dialog message here
    mBuilder.setCancelable(false)
        .setPositiveButton("OK",
            new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int id) {
                boolean isURL = Patterns.WEB_URL.matcher(userInputURL.getText().toString().trim()).matches();
                if (isURL) {
                  Intent mIntent = ExoPlayerActivity.getStartIntent(MainActivity.this, userInputURL.getText().toString().trim());
                  startActivity(mIntent);
                } else {
                  Toast.makeText(MainActivity.this, getString(R.string.error_message_url_not_valid), Toast.LENGTH_SHORT).show();
                }
              }
            })
        .setNegativeButton("Cancel",
            new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
              }
            }).create().show();
  }

5. Now MainActivity.java full code seems like below

package com.androidwave.exoplayer;

import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

  public class MainActivity extends AppCompatActivity {

    @BindView(R.id.buttonPlayUrlVideo)
    Button buttonPlayUrlVideo;
    @BindView(R.id.buttonPlayDefaultVideo)
    Button buttonPlayDefaultVideo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      getSupportActionBar().hide();
      ButterKnife.bind(this);
    }

    @OnClick({ R.id.buttonPlayUrlVideo, R.id.buttonPlayDefaultVideo })
    public void onViewClicked(View view) {
      switch (view.getId()) {
        case R.id.buttonPlayUrlVideo:
          showDialogPrompt();
          break;
        case R.id.buttonPlayDefaultVideo:
          Intent mIntent =
              ExoPlayerActivity.getStartIntent(this, VideoPlayerConfig.DEFAULT_VIDEO_URL);
          startActivity(mIntent);
          break;
      }
    }

    private void showDialogPrompt() {
      // get dialog_prompts.xml view
      LayoutInflater li = LayoutInflater.from(this);
      View promptsView = li.inflate(R.layout.dialog_prompts, null);
      AlertDialog.Builder mBuilder = new AlertDialog.Builder(this);
      // set dialog_prompts.xml to dialog
      mBuilder.setView(promptsView);
      final EditText userInputURL = (EditText) promptsView
          .findViewById(R.id.editTextDialogUrlInput);
      // set dialog message here
      mBuilder.setCancelable(false)
          .setPositiveButton("OK",
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                  boolean isURL =
                      Patterns.WEB_URL.matcher(userInputURL.getText().toString().trim()).matches();
                  if (isURL) {
                    Intent mIntent = ExoPlayerActivity.getStartIntent(MainActivity.this,
                        userInputURL.getText().toString().trim());
                    startActivity(mIntent);
                  } else {
                    Toast.makeText(MainActivity.this,
                        getString(R.string.error_message_url_not_valid), Toast.LENGTH_SHORT).show();
                  }
                }
              })
          .setNegativeButton("Cancel",
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                  dialog.cancel();
                }
              }).create().show();
    }
  }

6. Configure ExoPlayer

Before going to Exo Player activity we define followings configuration of ExoPlayer.
MIN_BUFFER_DURATION – Minimum Video you want to buffer while Playing. for now, I have set 3 second
MAX_BUFFER_DURATION – You can set Max Video you want to buffer during PlayBack.
MIN_PLAYBACK_START_BUFFER – Set Min Video you want to buffer before start Playing it
MIN_PLAYBACK_RESUME_BUFFER – Set Min video You want to buffer when the user resumes video
DEFAULT_VIDEO_URL – Set Default video url for demo

package com.androidwave.exoplayer;

  public class VideoPlayerConfig {
    //Minimum Video you want to buffer while Playing
    public static final int MIN_BUFFER_DURATION = 3000;
    //Max Video you want to buffer during PlayBack
    public static final int MAX_BUFFER_DURATION = 5000;
    //Min Video you want to buffer before start Playing it
    public static final int MIN_PLAYBACK_START_BUFFER = 1500;
    //Min video You want to buffer when user resumes video
    public static final int MIN_PLAYBACK_RESUME_BUFFER = 5000;

    public static final String DEFAULT_VIDEO_URL =
        "https://androidwave.com/media/androidwave-video-exo-player.mp4";
  }

7. Create a layout file name with activity_exo_player.xml

Create layout file inside res-> layout-> activity_exo_player.xml. and some components Progressbar(buffering indicator) and ExoPlayerView

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ExoPlayerActivity"
    >

  <com.google.android.exoplayer2.ui.PlayerView
      android:id="@+id/videoFullScreenPlayer"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:background="#A6000000"
      app:controller_layout_id="@layout/exo_playback_control_view"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="1.0"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintVertical_bias="1.0"
      app:player_layout_id="@layout/exo_simple_player_view"
      app:repeat_toggle_modes="none"
      app:show_timeout="45000"
      app:surface_type="texture_view"
      />

  <ProgressBar
      android:id="@+id/spinnerVideoDetails"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:indeterminate="true"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

  <ImageView
      android:id="@+id/imageViewExit"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="16dp"
      android:layout_marginLeft="16dp"
      android:layout_marginTop="16dp"
      android:padding="@dimen/default_item_padding"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:srcCompat="@drawable/ic_cross_circular_button_outline"
      />
</android.support.constraint.ConstraintLayout>

8. Same as Create ExoPlayerActivity.java

Go to create an Activity with ExoPlayerActivity.java and set content layout is activity_exo_player.xml. Furthermore generates ButteKnief Injection by select set ContentView and right click and click generate. Now one popup appeared select view and set ids and click ok.

  @BindView(R.id.videoFullScreenPlayer)
  PlayerView videoFullScreenPlayer;
  @BindView(R.id.spinnerVideoDetails)
  ProgressBar spinnerVideoDetails;
  @BindView(R.id.imageViewExit)
  ImageView imageViewExit;
8.1. Define some variables and do some fullscreen configuration
  private static final String TAG = "ExoPlayerActivity";
  private static final String KEY_VIDEO_URI = "video_uri";
  String videoUri;
  SimpleExoPlayer player;
  Handler mHandler;
  Runnable mRunnable;
8.2. We are using vector icon so enable
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
8.3. Now initialise video player configuration
  private void initializePlayer() {
    if (player == null) {
      // 1. Create a default TrackSelector
      LoadControl loadControl = new DefaultLoadControl(
          new DefaultAllocator(true, 16),
          VideoPlayerConfig.MIN_BUFFER_DURATION,
          VideoPlayerConfig.MAX_BUFFER_DURATION,
          VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER,
          VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER, -1, true);

      BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
      TrackSelection.Factory videoTrackSelectionFactory =
          new AdaptiveTrackSelection.Factory(bandwidthMeter);
      TrackSelector trackSelector =
          new DefaultTrackSelector(videoTrackSelectionFactory);
      // 2. Create the player
      player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), trackSelector,
          loadControl);
      videoFullScreenPlayer.setPlayer(player);
    }
  }
8.3. After inilizaing player create a media source using below methods
  private void buildMediaSource(Uri mUri) {
    // Measures bandwidth during playback. Can be null if not required.
    DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    // Produces DataSource instances through which media data is loaded.
    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
        Util.getUserAgent(this, getString(R.string.app_name)), bandwidthMeter);
    // This is the MediaSource representing the media to be played.
    MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
        .createMediaSource(mUri);
    // Prepare the player with the source.
    player.prepare(videoSource);
    player.setPlayWhenReady(true);
    player.addListener(this);
  }
8.4. Implement Player.EventListener and override unimplemented methods

Just implement Player.EventListener you have to implement override methods. onPlayerStateChanged() methods will return all player state.

  @Override
  public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
    switch (playbackState) {

      case Player.STATE_BUFFERING:
        spinnerVideoDetails.setVisibility(View.VISIBLE);
        break;
      case Player.STATE_ENDED:
        // Activate the force enable
        break;
      case Player.STATE_IDLE:

        break;
      case Player.STATE_READY:
        spinnerVideoDetails.setVisibility(View.GONE);

        break;
      default:
        // status = PlaybackStatus.IDLE;
        break;
    }
  }

9. Finally, The full activity (ExoPlayerActivity.java) code looks like this

Mainly we are doing below things in this activity

  • Initialize Player with configuration .
  • Prepare source files and settings on the player.
  • Add Player Event listener for listening to different player state
  • Manage player state with activity life cycler eg. pausePlayer(), resumePlayer(), releasePlayer()
package com.androidwave.exoplayer;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

  public class ExoPlayerActivity extends AppCompatActivity implements Player.EventListener {
    private static final String TAG = "ExoPlayerActivity";

    private static final String KEY_VIDEO_URI = "video_uri";

    @BindView(R.id.videoFullScreenPlayer)
    PlayerView videoFullScreenPlayer;
    @BindView(R.id.spinnerVideoDetails)
    ProgressBar spinnerVideoDetails;
    @BindView(R.id.imageViewExit)
    ImageView imageViewExit;

    String videoUri;
    SimpleExoPlayer player;
    Handler mHandler;
    Runnable mRunnable;

    public static Intent getStartIntent(Context context, String videoUri) {
      Intent intent = new Intent(context, ExoPlayerActivity.class);
      intent.putExtra(KEY_VIDEO_URI, videoUri);
      return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      this.getWindow()
          .setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
              WindowManager.LayoutParams.FLAG_FULLSCREEN);
      setContentView(R.layout.activity_exo_player);
      ButterKnife.bind(this);
      AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
      getSupportActionBar().hide();

      if (getIntent().hasExtra(KEY_VIDEO_URI)) {
        videoUri = getIntent().getStringExtra(KEY_VIDEO_URI);
      }

      setUp();
    }

    private void setUp() {
      initializePlayer();
      if (videoUri == null) {
        return;
      }
      buildMediaSource(Uri.parse(videoUri));
    }

    @OnClick(R.id.imageViewExit)
    public void onViewClicked() {
      finish();
    }

    private void initializePlayer() {
      if (player == null) {
        // 1. Create a default TrackSelector
        LoadControl loadControl = new DefaultLoadControl(
            new DefaultAllocator(true, 16),
            VideoPlayerConfig.MIN_BUFFER_DURATION,
            VideoPlayerConfig.MAX_BUFFER_DURATION,
            VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER,
            VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER, -1, true);

        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        TrackSelection.Factory videoTrackSelectionFactory =
            new AdaptiveTrackSelection.Factory(bandwidthMeter);
        TrackSelector trackSelector =
            new DefaultTrackSelector(videoTrackSelectionFactory);
        // 2. Create the player
        player =
            ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), trackSelector,
                loadControl);
        videoFullScreenPlayer.setPlayer(player);
      }
    }

    private void buildMediaSource(Uri mUri) {
      // Measures bandwidth during playback. Can be null if not required.
      DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
      // Produces DataSource instances through which media data is loaded.
      DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
          Util.getUserAgent(this, getString(R.string.app_name)), bandwidthMeter);
      // This is the MediaSource representing the media to be played.
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(mUri);
      // Prepare the player with the source.
      player.prepare(videoSource);
      player.setPlayWhenReady(true);
      player.addListener(this);
    }

    private void releasePlayer() {
      if (player != null) {
        player.release();
        player = null;
      }
    }

    private void pausePlayer() {
      if (player != null) {
        player.setPlayWhenReady(false);
        player.getPlaybackState();
      }
    }

    private void resumePlayer() {
      if (player != null) {
        player.setPlayWhenReady(true);
        player.getPlaybackState();
      }
    }

    @Override
    protected void onPause() {
      super.onPause();
      pausePlayer();
      if (mRunnable != null) {
        mHandler.removeCallbacks(mRunnable);
      }
    }

    @Override
    protected void onRestart() {
      super.onRestart();
      resumePlayer();
    }

    @Override
    protected void onDestroy() {
      super.onDestroy();
      releasePlayer();
    }

    @Override
    public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {

    }

    @Override
    public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {

    }

    @Override
    public void onLoadingChanged(boolean isLoading) {

    }

    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
      switch (playbackState) {

        case Player.STATE_BUFFERING:
          spinnerVideoDetails.setVisibility(View.VISIBLE);
          break;
        case Player.STATE_ENDED:
          // Activate the force enable
          break;
        case Player.STATE_IDLE:

          break;
        case Player.STATE_READY:
          spinnerVideoDetails.setVisibility(View.GONE);

          break;
        default:
          // status = PlaybackStatus.IDLE;
          break;
      }
    }

    @Override
    public void onRepeatModeChanged(int repeatMode) {

    }

    @Override
    public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

    }

    @Override
    public void onPlayerError(ExoPlaybackException error) {

    }

    @Override
    public void onPositionDiscontinuity(int reason) {

    }

    @Override
    public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

    }

    @Override
    public void onSeekProcessed() {

    }
  }

Conclusion

Wow! Great job! You just finished the ExoPlayer android example. You learned about integrating ExoPlayer in your application.

Download Sample Project- Video Streaming ExoPlayer in Android

If you have any queries, feel free to connect us.

In the previous tutorial, we demonstrate video streaming over the Internet using ExoPlayer. Somehow you missed previous articles you must read this ExoPlayer in Android for better understanding. In another article we saw how to play audio, video from SD card (local) as well, So you get an idea about ExoPlayer core functionality, DASH and UI library module.

I hope you read my previous tutorials(I can assume you have basic knowledge about ExoPlayer). In this tutorial, I will describe how to implement video playback in the RecyclerView Android. Just like some popular application eg. Magisto, Facebook, Twitter, Instagram.

This article based on ExoPlayer. In case some things missing in this article download full source and a working sample app is there.

Before starting code, Think about the problem statement. Basically, The following major challenges for us to implementing ExoPlayer in RecyclerView.

  • The first problem is managing ExoPlayer playback in RecyclerView
  • Second is we need to know which view on the scrolled list is currently active. Other words you can say which list item is visible 100%. So we can attach the player with a particular view.

Step to implement ExoPlayer in RecyclerView

  • Create Project and set up dependencies
  • Write a custom RecyclerView that are capable of manage video playback.
  • Write an adapter for holding list item
    • Create a model class with getter setter
    • Prepare an XML layout for media list item
    • Write ViewHolder class for representing media list item
    • Complete the RecyclerView Adapter
  • Do followings operation in MainActivity
    • In MainActivity layout, file adds custom ExoRecyclerView that create earlier.
    • Prepare demo content
    • Set the adapter to RecyclerView
    • Run the project

1. Create Project and set up dependencies

All conceptual part is done, let’s move to AndroidStudio create a new project with the default template. Follows few steps your project will be created, now we need to add some dependencies in app/gradle file.

Following dependencies are in this android app tutorial. I hope you already know the uses of use one.

  • RecyclerView – For using RecyclerView
  • ExoPlayer – Using ExoPlayer
  • Glide – For loading media thumbnails
  • Constraint Layout – Performace is much better to other layouts
1.1 Open the app/build.gradle file and add all above dependencies
  // RecyclerView
  implementation 'com.android.support:recyclerview-v7:28.0.0'

  // ExoPlayer
  implementation 'com.google.android.exoplayer:exoplayer:2.7.3'
  // Image loading
  implementation 'com.github.bumptech.glide:glide:4.9.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
1.2 Enable vector drawable by adding below line inside defaultConfig bracket
  vectorDrawables.useSupportLibrary = true

2. Write a custom RecyclerView that is capable manage video playback.

Doing that we have to create a new subclass of RecyclerView named is ExoPlayerRecyclerView. Mean create a new class ExoPlayerRecyclerView.java which extends RecyclerView. In this class, we have to manage two things. First, manage ExoPlayer playback. Second is to find that list item that visibility 100%. So that we will add a player on that particular view.

2.1 Problem – how to manage ExoPlayer playback?
  • Create a class ExoPlayerRecyclerView.java which extend RecyclerView 
  • Get start position and end position of view from LayoutManager after that calculate currently visible view position.
  • Here now on target position, we will add ExoPlayerView and set ExoPlayer.
  //play the video in the row
  public void playVideo(boolean isEndOfList) {

    int targetPosition;

    if (!isEndOfList) {
      int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
          getLayoutManager())).findFirstVisibleItemPosition();
      int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

      // if there is more than 2 list-items on the screen, set the difference to be 1
      if (endPosition - startPosition > 1) {
        endPosition = startPosition + 1;
      }

      // something is wrong. return.
      if (startPosition < 0 || endPosition < 0) {
        return;
      }

      // if there is more than 1 list-item on the screen
      if (startPosition != endPosition) {
        int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
        int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);

        targetPosition =
            startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
      } else {
        targetPosition = startPosition;
      }
    } else {
      targetPosition = mediaObjects.size() - 1;
    }

    Log.d(TAG, "playVideo: target position: " + targetPosition);

    // video is already playing so return
    if (targetPosition == playPosition) {
      return;
    }

    // set the position of the list-item that is to be played
    playPosition = targetPosition;
    if (videoSurfaceView == null) {
      return;
    }

    // remove any old surface views from previously playing videos
    videoSurfaceView.setVisibility(INVISIBLE);
    removeVideoView(videoSurfaceView);

    int currentPosition =
        targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
            getLayoutManager())).findFirstVisibleItemPosition();

    View child = getChildAt(currentPosition);
    if (child == null) {
      return;
    }

    PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
    if (holder == null) {
      playPosition = -1;
      return;
    }
    mediaCoverImage = holder.mediaCoverImage;
    progressBar = holder.progressBar;
    volumeControl = holder.volumeControl;
    viewHolderParent = holder.itemView;
    requestManager = holder.requestManager;
    mediaContainer = holder.mediaContainer;

    videoSurfaceView.setPlayer(videoPlayer);
    viewHolderParent.setOnClickListener(videoViewClickListener);

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
        context, Util.getUserAgent(context, AppName));
    String mediaUrl = mediaObjects.get(targetPosition).getUrl();
    if (mediaUrl != null) {
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(Uri.parse(mediaUrl));
      videoPlayer.prepare(videoSource);
      videoPlayer.setPlayWhenReady(true);
    }
  }

  andwidthMeter =new

  DefaultBandwidthMeter();
  // Produces DataSource instances through which media data is loaded.

  DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(appContext,
      Util.getUserAgent(appContext, "android_wave_list"), defaultBandwidthMeter);
  // This is the MediaSource representing the media to be played.
  String uriString = videoInfoList.get(targetPosition).getUrl();
        if(uriString !=null)

  {
    MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
        .createMediaSource(Uri.parse(uriString));
    // Prepare the player with the source.
    player.prepare(videoSource);
    player.setPlayWhenReady(true);
  }
}
2.2 Second Problem – Get visible video surface scrolled list.
 /**
   * Returns the visible region of the video surface on the screen.
   * if some is cut off, it will return less than the @videoSurfaceDefaultHeight
   */
  private int getVisibleVideoSurfaceHeight(int playPosition) {
    int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
        getLayoutManager())).findFirstVisibleItemPosition();
    Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);

    View child = getChildAt(at);
    if (child == null) {
      return 0;
    }

    int[] location = new int[2];
    child.getLocationInWindow(location);

    if (location[1] < 0) {
      return location[1] + videoSurfaceDefaultHeight;
    } else {
      return screenDefaultHeight - location[1];
    }
  }

I have solved both problems, The complete code of ExoPlayerRecyclerView is

package com.androidwave.exoplayer.ui;

import android.content.Context;
import android.graphics.Point;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.adapter.PlayerViewHolder;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Objects;

/**
 * Created on : May 24, 2019
 * Author     : AndroidWave
 */
public class ExoPlayerRecyclerView extends RecyclerView {

  private static final String TAG = "ExoPlayerRecyclerView";
  private static final String AppName = "Android ExoPlayer";
  /**
   * PlayerViewHolder UI component
   * Watch PlayerViewHolder class
   */
  private ImageView mediaCoverImage, volumeControl;
  private ProgressBar progressBar;
  private View viewHolderParent;
  private FrameLayout mediaContainer;
  private PlayerView videoSurfaceView;
  private SimpleExoPlayer videoPlayer;
  /**
   * variable declaration
   */
  // Media List
  private ArrayList<MediaObject> mediaObjects = new ArrayList<>();
  private int videoSurfaceDefaultHeight = 0;
  private int screenDefaultHeight = 0;
  private Context context;
  private int playPosition = -1;
  private boolean isVideoViewAdded;
  private RequestManager requestManager;
  // controlling volume state
  private VolumeState volumeState;
  private OnClickListener videoViewClickListener = new OnClickListener() {
    @Override
    public void onClick(View v) {
      toggleVolume();
    }
  };

  public ExoPlayerRecyclerView(@NonNull Context context) {
    super(context);
    init(context);
  }

  public ExoPlayerRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init(context);
  }

  private void init(Context context) {
    this.context = context.getApplicationContext();
    Display display = ((WindowManager) Objects.requireNonNull(
        getContext().getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay();
    Point point = new Point();
    display.getSize(point);

    videoSurfaceDefaultHeight = point.x;
    screenDefaultHeight = point.y;

    videoSurfaceView = new PlayerView(this.context);
    videoSurfaceView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);

    BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    TrackSelection.Factory videoTrackSelectionFactory =
        new AdaptiveTrackSelection.Factory(bandwidthMeter);
    TrackSelector trackSelector =
        new DefaultTrackSelector(videoTrackSelectionFactory);

    //Create the player using ExoPlayerFactory
    videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
    // Disable Player Control
    videoSurfaceView.setUseController(false);
    // Bind the player to the view.
    videoSurfaceView.setPlayer(videoPlayer);
    // Turn on Volume
    setVolumeControl(VolumeState.ON);

    addOnScrollListener(new RecyclerView.OnScrollListener() {
      @Override
      public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
          if (mediaCoverImage != null) {
            // show the old thumbnail
            mediaCoverImage.setVisibility(VISIBLE);
          }

          // There's a special case when the end of the list has been reached.
          // Need to handle that with this bit of logic
          if (!recyclerView.canScrollVertically(1)) {
            playVideo(true);
          } else {
            playVideo(false);
          }
        }
      }

      @Override
      public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
      }
    });

    addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
      @Override
      public void onChildViewAttachedToWindow(@NonNull View view) {

      }

      @Override
      public void onChildViewDetachedFromWindow(@NonNull View view) {
        if (viewHolderParent != null && viewHolderParent.equals(view)) {
          resetVideoView();
        }
      }
    });

    videoPlayer.addListener(new Player.EventListener() {
      @Override
      public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {

      }

      @Override
      public void onTracksChanged(TrackGroupArray trackGroups,
          TrackSelectionArray trackSelections) {

      }

      @Override
      public void onLoadingChanged(boolean isLoading) {

      }

      @Override
      public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        switch (playbackState) {

          case Player.STATE_BUFFERING:
            Log.e(TAG, "onPlayerStateChanged: Buffering video.");
            if (progressBar != null) {
              progressBar.setVisibility(VISIBLE);
            }

            break;
          case Player.STATE_ENDED:
            Log.d(TAG, "onPlayerStateChanged: Video ended.");
            videoPlayer.seekTo(0);
            break;
          case Player.STATE_IDLE:

            break;
          case Player.STATE_READY:
            Log.e(TAG, "onPlayerStateChanged: Ready to play.");
            if (progressBar != null) {
              progressBar.setVisibility(GONE);
            }
            if (!isVideoViewAdded) {
              addVideoView();
            }
            break;
          default:
            break;
        }
      }

      @Override
      public void onRepeatModeChanged(int repeatMode) {

      }

      @Override
      public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

      }

      @Override
      public void onPlayerError(ExoPlaybackException error) {

      }

      @Override
      public void onPositionDiscontinuity(int reason) {

      }

      @Override
      public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

      }

      @Override
      public void onSeekProcessed() {

      }
    });
  }

  public void playVideo(boolean isEndOfList) {

    int targetPosition;

    if (!isEndOfList) {
      int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
          getLayoutManager())).findFirstVisibleItemPosition();
      int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

      // if there is more than 2 list-items on the screen, set the difference to be 1
      if (endPosition - startPosition > 1) {
        endPosition = startPosition + 1;
      }

      // something is wrong. return.
      if (startPosition < 0 || endPosition < 0) {
        return;
      }

      // if there is more than 1 list-item on the screen
      if (startPosition != endPosition) {
        int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
        int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);

        targetPosition =
            startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
      } else {
        targetPosition = startPosition;
      }
    } else {
      targetPosition = mediaObjects.size() - 1;
    }

    Log.d(TAG, "playVideo: target position: " + targetPosition);

    // video is already playing so return
    if (targetPosition == playPosition) {
      return;
    }

    // set the position of the list-item that is to be played
    playPosition = targetPosition;
    if (videoSurfaceView == null) {
      return;
    }

    // remove any old surface views from previously playing videos
    videoSurfaceView.setVisibility(INVISIBLE);
    removeVideoView(videoSurfaceView);

    int currentPosition =
        targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
            getLayoutManager())).findFirstVisibleItemPosition();

    View child = getChildAt(currentPosition);
    if (child == null) {
      return;
    }

    PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
    if (holder == null) {
      playPosition = -1;
      return;
    }
    mediaCoverImage = holder.mediaCoverImage;
    progressBar = holder.progressBar;
    volumeControl = holder.volumeControl;
    viewHolderParent = holder.itemView;
    requestManager = holder.requestManager;
    mediaContainer = holder.mediaContainer;

    videoSurfaceView.setPlayer(videoPlayer);
    viewHolderParent.setOnClickListener(videoViewClickListener);

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
        context, Util.getUserAgent(context, AppName));
    String mediaUrl = mediaObjects.get(targetPosition).getUrl();
    if (mediaUrl != null) {
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(Uri.parse(mediaUrl));
      videoPlayer.prepare(videoSource);
      videoPlayer.setPlayWhenReady(true);
    }
  }

  /**
   * Returns the visible region of the video surface on the screen.
   * if some is cut off, it will return less than the @videoSurfaceDefaultHeight
   */
  private int getVisibleVideoSurfaceHeight(int playPosition) {
    int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
        getLayoutManager())).findFirstVisibleItemPosition();
    Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);

    View child = getChildAt(at);
    if (child == null) {
      return 0;
    }

    int[] location = new int[2];
    child.getLocationInWindow(location);

    if (location[1] < 0) {
      return location[1] + videoSurfaceDefaultHeight;
    } else {
      return screenDefaultHeight - location[1];
    }
  }

  // Remove the old player
  private void removeVideoView(PlayerView videoView) {
    ViewGroup parent = (ViewGroup) videoView.getParent();
    if (parent == null) {
      return;
    }

    int index = parent.indexOfChild(videoView);
    if (index >= 0) {
      parent.removeViewAt(index);
      isVideoViewAdded = false;
      viewHolderParent.setOnClickListener(null);
    }
  }

  private void addVideoView() {
    mediaContainer.addView(videoSurfaceView);
    isVideoViewAdded = true;
    videoSurfaceView.requestFocus();
    videoSurfaceView.setVisibility(VISIBLE);
    videoSurfaceView.setAlpha(1);
    mediaCoverImage.setVisibility(GONE);
  }

  private void resetVideoView() {
    if (isVideoViewAdded) {
      removeVideoView(videoSurfaceView);
      playPosition = -1;
      videoSurfaceView.setVisibility(INVISIBLE);
      mediaCoverImage.setVisibility(VISIBLE);
    }
  }

  public void releasePlayer() {

    if (videoPlayer != null) {
      videoPlayer.release();
      videoPlayer = null;
    }

    viewHolderParent = null;
  }

  public void onPausePlayer() {
    if (videoPlayer != null) {
      videoPlayer.stop(true);
    }
  }

  private void toggleVolume() {
    if (videoPlayer != null) {
      if (volumeState == VolumeState.OFF) {
        Log.d(TAG, "togglePlaybackState: enabling volume.");
        setVolumeControl(VolumeState.ON);
      } else if (volumeState == VolumeState.ON) {
        Log.d(TAG, "togglePlaybackState: disabling volume.");
        setVolumeControl(VolumeState.OFF);
      }
    }
  }

  private void setVolumeControl(VolumeState state) {
    volumeState = state;
    if (state == VolumeState.OFF) {
      videoPlayer.setVolume(0f);
      animateVolumeControl();
    } else if (state == VolumeState.ON) {
      videoPlayer.setVolume(1f);
      animateVolumeControl();
    }
  }

  private void animateVolumeControl() {
    if (volumeControl != null) {
      volumeControl.bringToFront();
      if (volumeState == VolumeState.OFF) {
        requestManager.load(R.drawable.ic_volume_off)
            .into(volumeControl);
      } else if (volumeState == VolumeState.ON) {
        requestManager.load(R.drawable.ic_volume_on)
            .into(volumeControl);
      }
      volumeControl.animate().cancel();

      volumeControl.setAlpha(1f);

      volumeControl.animate()
          .alpha(0f)
          .setDuration(600).setStartDelay(1000);
    }
  }

  public void setMediaObjects(ArrayList<MediaObject> mediaObjects) {
    this.mediaObjects = mediaObjects;
  }

  /**
   * Volume ENUM
   */
  private enum VolumeState {
    ON, OFF
  }
}

Now ExoPlayerRecyclerView.java component is ready to use. Now I will explain how to use this component.

3. Write an adapter for holding list item

Before moving ahead we need to write some helper class for RecyclerView adapter,

3.1 Create a model class with getter setter

Go to the src folder and create the model folder for separating all model classes. Inside this folder create a POJO class named MediaObject.java. That has getter and setter of title and cover image URL and media URL

package com.androidwave.exoplayer.model;

/**
 * Created by Morris on 03,June,2019
 */
public class MediaObject {
  private int uId;
  private String title;
  private String mediaUrl;
  private String mediaCoverImgUrl;
  private String userHandle;

  public String getUserHandle() {
    return userHandle;
  }

  public void setUserHandle(String mUserHandle) {
    this.userHandle = mUserHandle;
  }

  public int getId() {
    return uId;
  }

  public void setId(int uId) {
    this.uId = uId;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String mTitle) {
    this.title = mTitle;
  }

  public String getUrl() {
    return mediaUrl;
  }

  public void setUrl(String mUrl) {
    this.mediaUrl = mUrl;
  }

  public String getCoverUrl() {
    return mediaCoverImgUrl;
  }

  public void setCoverUrl(String mCoverUrl) {
    this.mediaCoverImgUrl = mCoverUrl;
  }
}
3.2 Prepare an XML layout for media list item

Create a layout file inside the res => layout folder named is layout_media_list_item.xml, which holds the view of the list item. So add some view component in this layout file.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:background="@color/white"
    >

  <TextView
      android:id="@+id/tvTitle"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginEnd="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:letterSpacing="-0.02"
      android:lineSpacingExtra="5sp"
      android:textColor="@color/navy"
      android:textSize="18sp"
      android:textStyle="bold"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      tools:text="Was india better then south africa in current serise?"
      />

  <ImageView
      android:id="@+id/imageView"
      android:layout_width="25dp"
      android:layout_height="25dp"
      android:layout_marginBottom="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      app:layout_constraintBottom_toTopOf="@+id/mediaContainer"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/tvTitle"
      app:srcCompat="@drawable/ic_user"
      />

  <TextView
      android:id="@+id/tvUserHandle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:letterSpacing="-0.02"
      android:textColor="@color/windows_blue"
      android:textSize="10sp"
      app:layout_constraintBottom_toBottomOf="@+id/imageView"
      app:layout_constraintStart_toEndOf="@+id/imageView"
      app:layout_constraintTop_toTopOf="@+id/imageView"
      tools:text="by @morris12"
      />

  <FrameLayout
      android:id="@+id/mediaContainer"
      android:layout_width="match_parent"
      android:layout_height="300dp"
      android:layout_marginBottom="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginStart="8dp"
      android:adjustViewBounds="true"
      android:background="@android:color/black"
      android:gravity="center"
      android:scaleType="center"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/imageView"
      >

    <ImageView
        android:id="@+id/ivMediaCoverImage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:scaleType="centerCrop"
        android:src="@drawable/cover"
        />


    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"
        style="?android:attr/progressBarStyle"
        />
    <ImageView
        android:id="@+id/ivVolumeControl"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_gravity="end|bottom"
        android:layout_marginBottom="15dp"
        android:layout_marginEnd="15dp"
        android:alpha="0"
        android:animateLayoutChanges="true"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_volume_on"
        />
  </FrameLayout>

</android.support.constraint.ConstraintLayout>
3.4 Write ViewHolder class for representing media list item

Create a new class inside the adapter package named is PlayerViewHolder. This class will extends RecyclerView.ViewHolder. Initialize the all list item inside this view.

package com.androidwave.exoplayer.adapter;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;

/**
 * Created by Morris on 03,June,2019
 */
public class PlayerViewHolder extends RecyclerView.ViewHolder {

  /**
   * below view have public modifier because
   * we have access PlayerViewHolder inside the ExoPlayerRecyclerView
   */
  public FrameLayout mediaContainer;
  public ImageView mediaCoverImage, volumeControl;
  public ProgressBar progressBar;
  public RequestManager requestManager;
  private TextView title, userHandle;
  private View parent;

  public PlayerViewHolder(@NonNull View itemView) {
    super(itemView);
    parent = itemView;
    mediaContainer = itemView.findViewById(R.id.mediaContainer);
    mediaCoverImage = itemView.findViewById(R.id.ivMediaCoverImage);
    title = itemView.findViewById(R.id.tvTitle);
    userHandle = itemView.findViewById(R.id.tvUserHandle);
    progressBar = itemView.findViewById(R.id.progressBar);
    volumeControl = itemView.findViewById(R.id.ivVolumeControl);
  }

  void onBind(MediaObject mediaObject, RequestManager requestManager) {
    this.requestManager = requestManager;
    parent.setTag(this);
    title.setText(mediaObject.getTitle());
    userHandle.setText(mediaObject.getUserHandle());
    this.requestManager
        .load(mediaObject.getCoverUrl())
        .into(mediaCoverImage);
  }
}
3.5 Complete the RecyclerView Adapter

Doing that create an adapter class named is MediaRecyclerAdapter and paste below code

package com.androidwave.exoplayer.adapter;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;

/**
 * Created by Morris on 03,June,2019
 */
public class MediaRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

  private ArrayList<MediaObject> mediaObjects;
  private RequestManager requestManager;

  public MediaRecyclerAdapter(ArrayList<MediaObject> mediaObjects,
      RequestManager requestManager) {
    this.mediaObjects = mediaObjects;
    this.requestManager = requestManager;
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    return new PlayerViewHolder(
        LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.layout_media_list_item, viewGroup, false));
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
    ((PlayerViewHolder) viewHolder).onBind(mediaObjects.get(i), requestManager);
  }

  @Override
  public int getItemCount() {
    return mediaObjects.size();
  }
}

4. Do followings operation in MainActivity

All part is done, Now come to activity part.

4.1 Lets add ExoPlayerRecyclerView inside the XML file

If you selected some default template while creating a project, MainActivity.java and activity_main.xml class will automatically be created. Open the activity_main and add below code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    >

  <com.androidwave.exoplayer.ui.ExoPlayerRecyclerView
      android:id="@+id/exoPlayerRecyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#dedede"
      android:dividerHeight="8dp"
      />

</android.support.constraint.ConstraintLayout>
4.2 Prepare demo content

For displaying data on RecyclerView We have to prepare a demo content.

 private void prepareVideoList() {
    VideoInfo videoInfo = new VideoInfo();
    videoInfo.setId(1);
    videoInfo.setUserHandle("@h.pandya");
    videoInfo.setTitle("Do you think the concept of marriage will no longer exist in the future?");
    videoInfo.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-1.png");
    videoInfo.setUrl("https://androidwave.com/media/androidwave-video-1.mp4");

    VideoInfo videoInfo2 = new VideoInfo();
    videoInfo2.setId(2);
    videoInfo2.setUserHandle("@hardik.patel");
    videoInfo2.setTitle(
        "If my future husband doesn't cook food as good as my mother should I scold him?");
    videoInfo2.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-2.png");
    videoInfo2.setUrl("https://androidwave.com/media/androidwave-video-2.mp4");

    VideoInfo videoInfo3 = new VideoInfo();
    videoInfo3.setId(3);
    videoInfo3.setUserHandle("@arun.gandhi");
    videoInfo3.setTitle("Give your opinion about the Ayodhya temple controversy.");
    videoInfo3.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-3.png");
    videoInfo3.setUrl("https://androidwave.com/media/androidwave-video-3.mp4");

    VideoInfo videoInfo4 = new VideoInfo();
    videoInfo4.setId(4);
    videoInfo4.setUserHandle("@sachin.patel");
    videoInfo4.setTitle("When did kama founders find sex offensive to Indian traditions");
    videoInfo4.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-4.png");
    videoInfo4.setUrl("https://androidwave.com/media/androidwave-video-6.mp4");

    VideoInfo videoInfo5 = new VideoInfo();
    videoInfo5.setId(5);
    videoInfo5.setUserHandle("@monika.sharma");
    videoInfo5.setTitle("When did you last cry in front of someone?");
    videoInfo5.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-5.png");
    videoInfo5.setUrl("https://androidwave.com/media/androidwave-video-5.mp4");

    videoInfoList.add(videoInfo);
    videoInfoList.add(videoInfo2);
    videoInfoList.add(videoInfo3);
    videoInfoList.add(videoInfo4);
    videoInfoList.add(videoInfo5);
    videoInfoList.add(videoInfo);
    videoInfoList.add(videoInfo2);
    videoInfoList.add(videoInfo3);
    videoInfoList.add(videoInfo4);
    videoInfoList.add(videoInfo5);
  }

4.3 Finally MainActivity source code looks like

package com.androidwave.exoplayer;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import com.androidwave.exoplayer.adapter.MediaRecyclerAdapter;
import com.androidwave.exoplayer.model.MediaObject;
import com.androidwave.exoplayer.ui.ExoPlayerRecyclerView;
import com.androidwave.exoplayer.utils.DividerItemDecoration;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;

import static android.widget.LinearLayout.VERTICAL;

/**
 * Created by Morris on 03,June,2019
 */
public class MainActivity extends AppCompatActivity {

  ExoPlayerRecyclerView mRecyclerView;

  private ArrayList<MediaObject> mediaObjectList = new ArrayList<>();
  private MediaRecyclerAdapter mAdapter;
  private boolean firstTime = true;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    // Prepare demo content
    prepareVideoList();

    //set data object
    mRecyclerView.setMediaObjects(mediaObjectList);
    mAdapter = new MediaRecyclerAdapter(mediaObjectList, initGlide());

    //Set Adapter
    mRecyclerView.setAdapter(mAdapter);

    if (firstTime) {
      new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
          mRecyclerView.playVideo(false);
        }
      });
      firstTime = false;
    }
  }

  private void initView() {
    mRecyclerView = findViewById(R.id.exoPlayerRecyclerView);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this, VERTICAL, false));
    Drawable dividerDrawable = ContextCompat.getDrawable(this, R.drawable.divider_drawable);
    mRecyclerView.addItemDecoration(new DividerItemDecoration(dividerDrawable));
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
  }

  private RequestManager initGlide() {
    RequestOptions options = new RequestOptions();

    return Glide.with(this)
        .setDefaultRequestOptions(options);
  }

  @Override
  protected void onDestroy() {
    if (mRecyclerView != null) {
      mRecyclerView.releasePlayer();
    }
    super.onDestroy();
  }

  private void prepareVideoList() {
    MediaObject mediaObject = new MediaObject();
    mediaObject.setId(1);
    mediaObject.setUserHandle("@h.pandya");
    mediaObject.setTitle(
        "Do you think the concept of marriage will no longer exist in the future?");
    mediaObject.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-1.png");
    mediaObject.setUrl("https://androidwave.com/media/androidwave-video-1.mp4");

    MediaObject mediaObject2 = new MediaObject();
    mediaObject2.setId(2);
    mediaObject2.setUserHandle("@hardik.patel");
    mediaObject2.setTitle(
        "If my future husband doesn't cook food as good as my mother should I scold him?");
    mediaObject2.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-2.png");
    mediaObject2.setUrl("https://androidwave.com/media/androidwave-video-2.mp4");

    MediaObject mediaObject3 = new MediaObject();
    mediaObject3.setId(3);
    mediaObject3.setUserHandle("@arun.gandhi");
    mediaObject3.setTitle("Give your opinion about the Ayodhya temple controversy.");
    mediaObject3.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-3.png");
    mediaObject3.setUrl("https://androidwave.com/media/androidwave-video-3.mp4");

    MediaObject mediaObject4 = new MediaObject();
    mediaObject4.setId(4);
    mediaObject4.setUserHandle("@sachin.patel");
    mediaObject4.setTitle("When did kama founders find sex offensive to Indian traditions");
    mediaObject4.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-4.png");
    mediaObject4.setUrl("https://androidwave.com/media/androidwave-video-6.mp4");

    MediaObject mediaObject5 = new MediaObject();
    mediaObject5.setId(5);
    mediaObject5.setUserHandle("@monika.sharma");
    mediaObject5.setTitle("When did you last cry in front of someone?");
    mediaObject5.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-5.png");
    mediaObject5.setUrl("https://androidwave.com/media/androidwave-video-5.mp4");

    mediaObjectList.add(mediaObject);
    mediaObjectList.add(mediaObject2);
    mediaObjectList.add(mediaObject3);
    mediaObjectList.add(mediaObject4);
    mediaObjectList.add(mediaObject5);
    mediaObjectList.add(mediaObject);
    mediaObjectList.add(mediaObject2);
    mediaObjectList.add(mediaObject3);
    mediaObjectList.add(mediaObject4);
    mediaObjectList.add(mediaObject5);
  }
}

Conclusion

You almost finished. That’s all for ExoPlayer in RecyclerViiew. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development. I

Download Sample Project- ExoPlayer in RecyclerView in Android

If you have any queries, feel free to ask them in the comment section below. Happy Coding 🙂

Read more on android architecture components tutorials

Introduction

Camera2 API is an upgraded model of the Camera device. Today you look many apps with rich camera features in markets like Instagram and Snapchat.   In earlier, we used the camera for video and image capture.

In 2014 google introduce Camera2 API with lollipop version (API Version 21). I would like to suggest you must use Camera API 2 if not have version constraint. Camera2 API is not supported below 21 API.

Camera2 device model takes input request to capture a single frame and a single image as per user. Each request is processed in a order, multiple requests can process at a time.

If already integrate camera2 API in your project and you feel the video appears to be frozen in playback, read our another article Audio Video out of Sync in Issue

Prerequisite

  • Create a new Project
  • Ensure is  MinSDK 21
  • Request storage, mic and camera permission
  • Setup Camera2 API

Step-1. Project Setup

Create a new project in android studio from File Menu => New Project => Enter Project Name => Set min SDK 21 => Select EmptyActivity from template.

Step-2. First of all set Min SDK 21

You must set minSdkVersion 21.  Camera2 API is not supported below 21 API.    

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.androidwave.camera2video"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

Step-3. Furthermore, add below dependency Permission Request

3.1 Set below dependency for runtime permission request
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:support-v4:27.1.1'
    implementation 'com.android.support:design:27.1.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    // request permission
    implementation 'com.karumi:dexter:4.2.0'
    // ButterKnife Dependency Injection
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
3.2 Declare below permission in AndroidManifest.xml
 <! – declare storage, camera and audio permission -->
 <uses-permission android:name="android.permission.CAMERA"/>
 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.3 Request Camera Permission in runtime in activity
  /**
   * Requesting permissions storage, audio and camera at once
   */
  public void requestPermission() {
    Dexter.withActivity(getActivity())
        .withPermissions(Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
        .withListener(new MultiplePermissionsListener() {
          @Override
          public void onPermissionsChecked(MultiplePermissionsReport report) {
            // check if all permissions are granted or not
            if (report.areAllPermissionsGranted()) {
              if (mTextureView.isAvailable()) {
                openCamera(mTextureView.getWidth(), mTextureView.getHeight());
              } else {
                mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
              }
            }
            // check for permanent denial of any permission show alert dialog
            if (report.isAnyPermissionPermanentlyDenied()) {
              // open Settings activity
              showSettingsDialog();
            }
          }
          @Override
          public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions,
              PermissionToken token) {
            token.continuePermissionRequest();
          }
        })
        .withErrorListener(
            error -> Toast.makeText(getActivity().getApplicationContext(), "Error occurred! ",
                Toast.LENGTH_SHORT).show())
        .onSameThread()
        .check();
  }
  /**
   * Showing Alert Dialog with Settings option in case of deny any permission
   */
  private void showSettingsDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle(getString(R.string.message_need_permission));
    builder.setMessage(getString(R.string.message_permission));
    builder.setPositiveButton(getString(R.string.title_go_to_setting), (dialog, which) -> {
      dialog.cancel();
      openSettings();
    });
    builder.show();
  }
  // navigating settings app
  private void openSettings() {
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
    intent.setData(uri);
    startActivityForResult(intent, 101);
  }

Step-4. Create java file for camera preview name is AutoFitTextureView.java

Create a AutoFitTextureView which extends TextureView. The Java code looks Like this.

package com.androidwave.camera2video.camera;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
  public class AutoFitTextureView extends TextureView {
    private int mRatioWidth = 0;
    private int mRatioHeight = 0;
    public AutoFitTextureView(Context context) {
      this(context, null);
    }
    public AutoFitTextureView(Context context, AttributeSet attrs) {
      this(context, attrs, 0);
    }
    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
    }
    /**
     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
     *
     * @param width Relative horizontal size
     * @param height Relative vertical size
     */
    public void setAspectRatio(int width, int height) {
      if (width < 0 || height < 0) {
        throw new IllegalArgumentException("Size cannot be negative.");
      }
      mRatioWidth = width;
      mRatioHeight = height;
      requestLayout();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int width = MeasureSpec.getSize(widthMeasureSpec);
      int height = MeasureSpec.getSize(heightMeasureSpec);
      if (0 == mRatioWidth || 0 == mRatioHeight) {
        setMeasuredDimension(width, height);
      } else {
        if (width < height * mRatioWidth / mRatioHeight) {
          setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
        } else {
          setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
        }
      }
    }
  }

Step-5. Open Camera

Check if all permissions are granted then open camera using below code

  /**
   * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
   */
  private void openCamera(int width, int height) {
    final Activity activity = getActivity();
    if (null == activity || activity.isFinishing()) {
      return;
    }
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
      Log.d(TAG, "tryAcquire");
      if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
        throw new RuntimeException("Time out waiting to lock camera opening.");
      }
      /**
       * default front camera will activate
       */
      String cameraId = manager.getCameraIdList()[0];
      CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
      StreamConfigurationMap map = characteristics
          .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
      mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
      if (map == null) {
        throw new RuntimeException("Cannot get available preview/video sizes");
      }
      mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
      mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
          width, height, mVideoSize);
      int orientation = getResources().getConfiguration().orientation;
      if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
        mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      } else {
        mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
      }
      configureTransform(width, height);
      mMediaRecorder = new MediaRecorder();
      if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
          != PackageManager.PERMISSION_GRANTED) {
        // TODO: Consider calling
        requestPermission();
        return;
      }
      manager.openCamera(cameraId, mStateCallback, null);
    } catch (CameraAccessException e) {
      Log.e(TAG, "openCamera: Cannot access the camera.");
    } catch (NullPointerException e) {
      Log.e(TAG, "Camera2API is not supported on the device.");
    } catch (InterruptedException e) {
      throw new RuntimeException("Interrupted while trying to lock camera opening.");
    }
  }

Step-6. Choose the aspect ratio of video size

Choose the aspect ratio of video size. The video aspect ratio of the video should be 3×4 or 16×9. Also, we don’t use sizes larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.

  /**
   * In this sample, we choose a video size with 3x4 for  aspect ratio. for more perfectness 720 as
   * well Also, we don't use sizes
   * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
   *
   * @param choices The list of available sizes
   * @return The video size 1080p,720px
   */
  private static Size chooseVideoSize(Size[] choices) {
    for (Size size : choices) {
      if (1920 == size.getWidth() && 1080 == size.getHeight()) {
        return size;
      }
    }
    for (Size size : choices) {
      if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
        return size;
      }
    }
    Log.e(TAG, "Couldn't find any suitable video size");
    return choices[choices.length - 1];
  }
  /**
   * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
   * width and height are at least as large as the respective requested values, and whose aspect
   * ratio matches with the specified value.
   *
   * @param choices The list of sizes that the camera supports for the intended output class
   * @param width The minimum desired width
   * @param height The minimum desired height
   * @param aspectRatio The aspect ratio
   * @return The optimal {@code Size}, or an arbitrary one if none were big enough
   */
  private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
    // Collect the supported resolutions that are at least as big as the preview Surface
    List<Size> bigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
      if (option.getHeight() == option.getWidth() * h / w &&
          option.getWidth() >= width && option.getHeight() >= height) {
        bigEnough.add(option);
      }
    }
    // Pick the smallest of those, assuming we found any
    if (bigEnough.size() > 0) {
      return Collections.min(bigEnough, new CompareSizesByArea());
    } else {
      Log.e(TAG, "Couldn't find any suitable preview size");
      return choices[0];
    }
  }

Step-7. Show video previews

After allowing above permission render preview on AutofitTextureView

  /**
   * Start the camera preview.
   */
  private void startPreview() {
    if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
      return;
    }
    try {
      closePreviewSession();
      SurfaceTexture texture = mTextureView.getSurfaceTexture();
      assert texture != null;
      texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      Surface previewSurface = new Surface(texture);
      mPreviewBuilder.addTarget(previewSurface);
      mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
          new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
              mPreviewSession = session;
              updatePreview();
            }
            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
              Log.e(TAG, "onConfigureFailed: Failed ");
            }
          }, mBackgroundHandler);
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
  }

Step-8. Setup media recorder

So now you able to preview on screen. If we want to start video recording lets configure the MediaRecorder

  private void setUpMediaRecorder() throws IOException {
    final Activity activity = getActivity();
    if (null == activity) {
      return;
    }
    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    /**
     * create video output file
     */
    mCurrentFile = getOutputMediaFile();
    /**
     * set output file in media recorder
     */
    mMediaRecorder.setOutputFile(mCurrentFile.getAbsolutePath());
    CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
    mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
    mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
    mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
    mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
    mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
    mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    switch (mSensorOrientation) {
      case SENSOR_ORIENTATION_DEFAULT_DEGREES:
        mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
        break;
      case SENSOR_ORIENTATION_INVERSE_DEGREES:
        mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
        break;
    }
    mMediaRecorder.prepare();
  }

Step-9. After setting, media lets start Video Recording via below code

public void startRecordingVideo() {
    if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
      return;
    }
    try {
      closePreviewSession();
      setUpMediaRecorder();
      SurfaceTexture texture = mTextureView.getSurfaceTexture();
      assert texture != null;
      texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
      List<Surface> surfaces = new ArrayList<>();
      /**
       * Surface for the camera preview set up
       */
      Surface previewSurface = new Surface(texture);
      surfaces.add(previewSurface);
      mPreviewBuilder.addTarget(previewSurface);
      //MediaRecorder setup for surface
      Surface recorderSurface = mMediaRecorder.getSurface();
      surfaces.add(recorderSurface);
      mPreviewBuilder.addTarget(recorderSurface);
      // Start a capture session
      mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
          mPreviewSession = cameraCaptureSession;
          updatePreview();
          getActivity().runOnUiThread(() -> {
            mIsRecordingVideo = true;
            // Start recording
            mMediaRecorder.start();
          });
        }
        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
          Log.e(TAG, "onConfigureFailed: Failed");
        }
      }, mBackgroundHandler);
    } catch (CameraAccessException | IOException e) {
      e.printStackTrace();
    }
  }

Step-10. After that stop video record via below code.

  public void stopRecordingVideo() throws Exception {
    // UI
    mIsRecordingVideo = false;
    try {
      mPreviewSession.stopRepeating();
      mPreviewSession.abortCaptures();
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
    // Stop recording
    mMediaRecorder.stop();
    mMediaRecorder.reset();
  }
All things are done now Now CameraVideoFragment utility is ready to use. Just extend CameraVideoFragment class in place of Fragment. This final preview of CameraVideoFragment.java
package com.androidwave.camera2video.camera;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Toast;
import com.androidwave.camera2video.R;
import com.androidwave.camera2video.ui.base.BaseFragment;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
  public abstract class CameraVideoFragment extends BaseFragment {
    private static final String TAG = "CameraVideoFragment";
    private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    static {
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
      INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
    }
    static {
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
      DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
    }
    private File mCurrentFile;
    private static final String VIDEO_DIRECTORY_NAME = "AndroidWave";
    /**
     * An {@link AutoFitTextureView} for camera preview.
     */
    private AutoFitTextureView mTextureView;
    /**
     * A reference to the opened {@link CameraDevice}.
     */
    private CameraDevice mCameraDevice;
    /**
     * A reference to the current {@link CameraCaptureSession} for
     * preview.
     */
    private CameraCaptureSession mPreviewSession;
    /**
     * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
     * {@link TextureView}.
     */
    private TextureView.SurfaceTextureListener mSurfaceTextureListener
        = new TextureView.SurfaceTextureListener() {
      @Override
      public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
          int width, int height) {
        openCamera(width, height);
      }
      @Override
      public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
          int width, int height) {
        configureTransform(width, height);
      }
      @Override
      public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return true;
      }
      @Override
      public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
      }
    };
    /**
     * The {@link Size} of camera preview.
     */
    private Size mPreviewSize;
    /**
     * The {@link Size} of video recording.
     */
    private Size mVideoSize;
    /**
     * MediaRecorder
     */
    private MediaRecorder mMediaRecorder;
    /**
     * Whether the app is recording video now
     */
    public boolean mIsRecordingVideo;
    /**
     * An additional thread for running tasks that shouldn't block the UI.
     */
    private HandlerThread mBackgroundThread;
    /**
     * A {@link Handler} for running tasks in the background.
     */
    private Handler mBackgroundHandler;
    /**
     * A {@link Semaphore} to prevent the app from exiting before closing the camera.
     */
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
    /**
     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status.
     */
    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
      @Override
      public void onOpened(@NonNull CameraDevice cameraDevice) {
        mCameraDevice = cameraDevice;
        startPreview();
        mCameraOpenCloseLock.release();
        if (null != mTextureView) {
          configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
        }
      }
      @Override
      public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
      }
      @Override
      public void onError(@NonNull CameraDevice cameraDevice, int error) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
        Activity activity = getActivity();
        if (null != activity) {
          activity.finish();
        }
      }
    };
    private Integer mSensorOrientation;
    private CaptureRequest.Builder mPreviewBuilder;
    /**
     * In this sample, we choose a video size with 3x4 for  aspect ratio. for more perfectness 720
     * as well Also, we don't use sizes
     * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
     *
     * @param choices The list of available sizes
     * @return The video size 1080p,720px
     */
    private static Size chooseVideoSize(Size[] choices) {
      for (Size size : choices) {
        if (1920 == size.getWidth() && 1080 == size.getHeight()) {
          return size;
        }
      }
      for (Size size : choices) {
        if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
          return size;
        }
      }
      Log.e(TAG, "Couldn't find any suitable video size");
      return choices[choices.length - 1];
    }
    /**
     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
     * width and height are at least as large as the respective requested values, and whose aspect
     * ratio matches with the specified value.
     *
     * @param choices The list of sizes that the camera supports for the intended output class
     * @param width The minimum desired width
     * @param height The minimum desired height
     * @param aspectRatio The aspect ratio
     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
     */
    private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
      // Collect the supported resolutions that are at least as big as the preview Surface
      List<Size> bigEnough = new ArrayList<>();
      int w = aspectRatio.getWidth();
      int h = aspectRatio.getHeight();
      for (Size option : choices) {
        if (option.getHeight() == option.getWidth() * h / w &&
            option.getWidth() >= width && option.getHeight() >= height) {
          bigEnough.add(option);
        }
      }
      // Pick the smallest of those, assuming we found any
      if (bigEnough.size() > 0) {
        return Collections.min(bigEnough, new CompareSizesByArea());
      } else {
        Log.e(TAG, "Couldn't find any suitable preview size");
        return choices[0];
      }
    }
    public abstract int getTextureResource();
    @Override
    public void onViewCreated(final View view, Bundle savedInstanceState) {
      mTextureView = view.findViewById(getTextureResource());
    }
    @Override
    public void onResume() {
      super.onResume();
      startBackgroundThread();
      requestPermission();
    }
    @Override
    public void onPause() {
      closeCamera();
      stopBackgroundThread();
      super.onPause();
    }
    protected File getCurrentFile() {
      return mCurrentFile;
    }
    /**
     * Starts a background thread and its {@link Handler}.
     */
    private void startBackgroundThread() {
      mBackgroundThread = new HandlerThread("CameraBackground");
      mBackgroundThread.start();
      mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }
    /**
     * Stops the background thread and its {@link Handler}.
     */
    private void stopBackgroundThread() {
      mBackgroundThread.quitSafely();
      try {
        mBackgroundThread.join();
        mBackgroundThread = null;
        mBackgroundHandler = null;
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    /**
     * Requesting permissions storage, audio and camera at once
     */
    public void requestPermission() {
      Dexter.withActivity(getActivity())
          .withPermissions(Manifest.permission.CAMERA,
              Manifest.permission.RECORD_AUDIO,
              Manifest.permission.READ_EXTERNAL_STORAGE,
              Manifest.permission.WRITE_EXTERNAL_STORAGE)
          .withListener(new MultiplePermissionsListener() {
            @Override
            public void onPermissionsChecked(MultiplePermissionsReport report) {
              // check if all permissions are granted or not
              if (report.areAllPermissionsGranted()) {
                if (mTextureView.isAvailable()) {
                  openCamera(mTextureView.getWidth(), mTextureView.getHeight());
                } else {
                  mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
                }
              }
              // check for permanent denial of any permission show alert dialog
              if (report.isAnyPermissionPermanentlyDenied()) {
                // open Settings activity
                showSettingsDialog();
              }
            }
            @Override
            public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions,
                PermissionToken token) {
              token.continuePermissionRequest();
            }
          })
          .withErrorListener(
              error -> Toast.makeText(getActivity().getApplicationContext(), "Error occurred! ",
                  Toast.LENGTH_SHORT).show())
          .onSameThread()
          .check();
    }
    /**
     * Showing Alert Dialog with Settings option in case of deny any permission
     */
    private void showSettingsDialog() {
      AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
      builder.setTitle(getString(R.string.message_need_permission));
      builder.setMessage(getString(R.string.message_permission));
      builder.setPositiveButton(getString(R.string.title_go_to_setting), (dialog, which) -> {
        dialog.cancel();
        openSettings();
      });
      builder.show();
    }
    // navigating settings app
    private void openSettings() {
      Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
      Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
      intent.setData(uri);
      startActivityForResult(intent, 101);
    }
    /**
     * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
     */
    private void openCamera(int width, int height) {
      final Activity activity = getActivity();
      if (null == activity || activity.isFinishing()) {
        return;
      }
      CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
      try {
        Log.d(TAG, "tryAcquire");
        if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
          throw new RuntimeException("Time out waiting to lock camera opening.");
        }
        /**
         * default front camera will activate
         */
        String cameraId = manager.getCameraIdList()[0];
        CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap map = characteristics
            .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        if (map == null) {
          throw new RuntimeException("Cannot get available preview/video sizes");
        }
        mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
            width, height, mVideoSize);
        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
          mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        } else {
          mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
        }
        configureTransform(width, height);
        mMediaRecorder = new MediaRecorder();
        if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
          // TODO: Consider calling
          requestPermission();
          return;
        }
        manager.openCamera(cameraId, mStateCallback, null);
      } catch (CameraAccessException e) {
        Log.e(TAG, "openCamera: Cannot access the camera.");
      } catch (NullPointerException e) {
        Log.e(TAG, "Camera2API is not supported on the device.");
      } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera opening.");
      }
    }
    /**
     * Create directory and return file
     * returning video file
     */
    private File getOutputMediaFile() {
      // External sdcard file location
      File mediaStorageDir = new File(Environment.getExternalStorageDirectory(),
          VIDEO_DIRECTORY_NAME);
      // Create storage directory if it does not exist
      if (!mediaStorageDir.exists()) {
        if (!mediaStorageDir.mkdirs()) {
          Log.d(TAG, "Oops! Failed create "
              + VIDEO_DIRECTORY_NAME + " directory");
          return null;
        }
      }
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
          Locale.getDefault()).format(new Date());
      File mediaFile;
      mediaFile = new File(mediaStorageDir.getPath() + File.separator
          + "VID_" + timeStamp + ".mp4");
      return mediaFile;
    }
    /**
     * close camera and release object
     */
    private void closeCamera() {
      try {
        mCameraOpenCloseLock.acquire();
        closePreviewSession();
        if (null != mCameraDevice) {
          mCameraDevice.close();
          mCameraDevice = null;
        }
        if (null != mMediaRecorder) {
          mMediaRecorder.release();
          mMediaRecorder = null;
        }
      } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera closing.");
      } finally {
        mCameraOpenCloseLock.release();
      }
    }
    /**
     * Start the camera preview.
     */
    private void startPreview() {
      if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
        return;
      }
      try {
        closePreviewSession();
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        Surface previewSurface = new Surface(texture);
        mPreviewBuilder.addTarget(previewSurface);
        mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
            new CameraCaptureSession.StateCallback() {
              @Override
              public void onConfigured(@NonNull CameraCaptureSession session) {
                mPreviewSession = session;
                updatePreview();
              }
              @Override
              public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                Log.e(TAG, "onConfigureFailed: Failed ");
              }
            }, mBackgroundHandler);
      } catch (CameraAccessException e) {
        e.printStackTrace();
      }
    }
    /**
     * Update the camera preview. {@link #startPreview()} needs to be called in advance.
     */
    private void updatePreview() {
      if (null == mCameraDevice) {
        return;
      }
      try {
        setUpCaptureRequestBuilder(mPreviewBuilder);
        HandlerThread thread = new HandlerThread("CameraPreview");
        thread.start();
        mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
      } catch (CameraAccessException e) {
        e.printStackTrace();
      }
    }
    private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
      builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    }
    /**
     * Configures the necessary {@link Matrix} transformation to `mTextureView`.
     * This method should not to be called until the camera preview size is determined in
     * openCamera, or until the size of `mTextureView` is fixed.
     *
     * @param viewWidth The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    private void configureTransform(int viewWidth, int viewHeight) {
      Activity activity = getActivity();
      if (null == mTextureView || null == mPreviewSize || null == activity) {
        return;
      }
      int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
      Matrix matrix = new Matrix();
      RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
      RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
      float centerX = viewRect.centerX();
      float centerY = viewRect.centerY();
      if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale = Math.max(
            (float) viewHeight / mPreviewSize.getHeight(),
            (float) viewWidth / mPreviewSize.getWidth());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(90 * (rotation - 2), centerX, centerY);
      }
      mTextureView.setTransform(matrix);
    }
    private void setUpMediaRecorder() throws IOException {
      final Activity activity = getActivity();
      if (null == activity) {
        return;
      }
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      /**
       * create video output file
       */
      mCurrentFile = getOutputMediaFile();
      /**
       * set output file in media recorder
       */
      mMediaRecorder.setOutputFile(mCurrentFile.getAbsolutePath());
      CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
      mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
      mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
      mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
      mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
      mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
      mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
      int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
      switch (mSensorOrientation) {
        case SENSOR_ORIENTATION_DEFAULT_DEGREES:
          mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
          break;
        case SENSOR_ORIENTATION_INVERSE_DEGREES:
          mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
          break;
      }
      mMediaRecorder.prepare();
    }
    public void startRecordingVideo() {
      if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
        return;
      }
      try {
        closePreviewSession();
        setUpMediaRecorder();
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
        List<Surface> surfaces = new ArrayList<>();
        /**
         * Surface for the camera preview set up
         */
        Surface previewSurface = new Surface(texture);
        surfaces.add(previewSurface);
        mPreviewBuilder.addTarget(previewSurface);
        //MediaRecorder setup for surface
        Surface recorderSurface = mMediaRecorder.getSurface();
        surfaces.add(recorderSurface);
        mPreviewBuilder.addTarget(recorderSurface);
        // Start a capture session
        mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback()
        @Override
        public void onConfigured (@NonNull CameraCaptureSession cameraCaptureSession){
          mPreviewSession = cameraCaptureSession;
          updatePreview();
          getActivity().runOnUiThread(() -> {
            mIsRecordingVideo = true;
            // Start recording
            mMediaRecorder.start();
          });
        }
        @Override
        public void onConfigureFailed (@NonNull CameraCaptureSession cameraCaptureSession){
          Log.e(TAG, "onConfigureFailed: Failed");
        }
      },mBackgroundHandler);
    } catch(CameraAccessException |
    IOException e)
    {
      e.printStackTrace();
    }
  }
  private void closePreviewSession() {
    if (mPreviewSession != null) {
      mPreviewSession.close();
      mPreviewSession = null;
    }
  }
  public void stopRecordingVideo() throws Exception {
    // UI
    mIsRecordingVideo = false;
    try {
      mPreviewSession.stopRepeating();
      mPreviewSession.abortCaptures();
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
    // Stop recording
    mMediaRecorder.stop();
    mMediaRecorder.reset();
  }
  /**
   * Compares two {@code Size}s based on their areas.
   */
  static class CompareSizesByArea implements Comparator<Size> {
    @Override
    public int compare(Size lhs, Size rhs) {
      // We cast here to ensure the multiplications won't overflow
      return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
          (long) rhs.getWidth() * rhs.getHeight());
    }
  }
}

10. Now all utilities are done. Now comes on a real implementation.

10.1 Create a Fragment with the name of CameraFragment. Which extends CameraVideoFragment utility class
10.2 Create fragment_camera.xml. add below components
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CameraFragment"
    >
  <com.androidwave.camera2video.camera.AutoFitTextureView
      android:id="@+id/mTextureView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <ImageView
      android:id="@+id/mRecordVideo"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="24dp"
      android:contentDescription="@string/play_stop"
      android:src="@drawable/ic_record"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />
  <VideoView
      android:id="@+id/mVideoView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:visibility="gone"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <ImageView
      android:id="@+id/mPlayVideo"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:src="@drawable/ic_play_button"
      android:visibility="gone"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
</android.support.constraint.ConstraintLayout>
10.3. SetTextureResource by implement getTextureResource() methods of parent class
package com.androidwave.camera2video;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.VideoView;
import com.androidwave.camera2video.camera.AutoFitTextureView;
import com.androidwave.camera2video.camera.CameraVideoFragment;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
/**
 * A simple {@link Fragment} subclass.
 * Use the {@link CameraFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class CameraFragment extends CameraVideoFragment {
    @BindView(R.id.mTextureView)
    AutoFitTextureView mTextureView;
    @BindView(R.id.mRecordVideo)
    ImageView mRecordVideo;
    @BindView(R.id.mVideoView)
    VideoView mVideoView;
    @BindView(R.id.mPlayVideo)
    ImageView mPlayVideo;
    Unbinder unbinder;
    private String mOutputFilePath;
    public CameraFragment() {
        // Required empty public constructor
    }
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     */
    public static CameraFragment newInstance() {
        CameraFragment fragment = new CameraFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_camera, container, false);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }
    @Override
    public int getTextureResource() {
        return R.id.mTextureView;
    }
    @Override
    protected void setUp(View view) {
    }
    @OnClick({R.id.mRecordVideo, R.id.mPlayVideo})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.mRecordVideo:
                /**
                 * If media is not recoding then start recording else stop recording
                 */
                if (mIsRecordingVideo) {
                    try {
                        stopRecordingVideo();
                        prepareViews();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    startRecordingVideo();
                    mRecordVideo.setImageResource(R.drawable.ic_stop);
                    //Receive out put file here
                    mOutputFilePath = getCurrentFile().getAbsolutePath();
                }
                break;
            case R.id.mPlayVideo:
                mVideoView.start();
                mPlayVideo.setVisibility(View.GONE);
                break;
        }
    }
    private void prepareViews() {
        if (mVideoView.getVisibility() == View.GONE) {
            mVideoView.setVisibility(View.VISIBLE);
            mPlayVideo.setVisibility(View.VISIBLE);
            mTextureView.setVisibility(View.GONE);
            setMediaForRecordVideo();
        }
    }
    private void setMediaForRecordVideo() {
        // Set media controller
        mVideoView.setMediaController(new MediaController(getActivity()));
        mVideoView.requestFocus();
        mVideoView.setVideoPath(mOutputFilePath);
        mVideoView.seekTo(100);
        mVideoView.setOnCompletionListener(mp -> {
            // Reset player
            mVideoView.setVisibility(View.GONE);
            mTextureView.setVisibility(View.VISIBLE);
            mPlayVideo.setVisibility(View.GONE);
            mRecordVideo.setImageResource(R.drawable.ic_record);
        });
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }
}

Step-11. As a result Fragment in with Activity seems below.

package com.androidwave.camera2video;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().hide();
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
        if (null == savedInstanceState) {
            getFragmentManager().beginTransaction()
                    .replace(R.id.container, CameraFragment.newInstance())
                    .commit();
        }
    }
}

In this video recording android example, we have learned video recording using Camera2 API. Happy Coding 😁

Download Sample Project- Video Recording with Camera2 API in Android