Android Facebook SDK Login exception - authentication

I am following the tutorial of the official android SDK,and try to write a simple log in program.
So what I did is..
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.logintutorial"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="20" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="#string/app_id"/>
</manifest>
The MainActivity simply uses fragment transaction.
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Menu;
public class MainActivity extends FragmentActivity {
private MainFragment mainFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
// Add the fragment on initial activity setup
mainFragment = new MainFragment();
getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, mainFragment)
.commit();
} else {
// Or set the fragment from restored state info
mainFragment = (MainFragment) getSupportFragmentManager()
.findFragmentById(android.R.id.content);
}
}
}
The MainFragment class is used for the Session Management with the help of the UiLifecyleHelper and the Session.StatusCallBack class.
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.Session;
import com.facebook.SessionState;
import com.facebook.UiLifecycleHelper;
import com.facebook.widget.LoginButton;
public class MainFragment extends Fragment {
private static final String TAG = "MainFragment";
private UiLifecycleHelper uiHelper;
private Session.StatusCallback callback = new Session.StatusCallback() {
#Override
public void call(final Session session, final SessionState state, final Exception exception)
{
onSessionStateChange(session, state, exception);
}
};
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_main, container, false);
LoginButton authButton = (LoginButton) view.findViewById(R.id.authButton);
authButton.setFragment(this);
//authButton.setReadPermissions(Arrays.asList("user_likes", "user_status"));
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uiHelper = new UiLifecycleHelper(getActivity(), callback);
uiHelper.onCreate(savedInstanceState);
}
#Override
public void onResume() {
super.onResume();
// For scenarios where the main activity is launched and user
// session is not null, the session state change notification
// may not be triggered. Trigger it if it's open/closed.
Session session = Session.getActiveSession();
if (session != null &&
(session.isOpened() || session.isClosed()) ) {
onSessionStateChange(session, session.getState(), null);
}
uiHelper.onResume();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
uiHelper.onActivityResult(requestCode, resultCode, data);
}
#Override
public void onPause() {
super.onPause();
uiHelper.onPause();
}
#Override
public void onDestroy() {
super.onDestroy();
uiHelper.onDestroy();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
uiHelper.onSaveInstanceState(outState);
}
private void onSessionStateChange(Session session, SessionState state, Exception exception) {
if (state.isOpened()) {
Log.i(TAG, "Logged in...");
} else if (state.isClosed()) {
Log.i(TAG, "Logged out...");
}
}
}
Also the main activity's xml file...
<LinearLayout 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"
android:orientation="vertical">
<com.facebook.widget.LoginButton
android:id="#+id/authButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
/>
</LinearLayout>
and finally I put the app id in the string.xml file as
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Login Tutorial</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="app_id">XXX6999249XXXXX</string>
</resources>
When I run it, I get the following exception.
11-10 13:01:46.784: E/AndroidRuntime(31157): FATAL EXCEPTION: main
11-10 13:01:46.784: E/AndroidRuntime(31157): java.lang.RuntimeException:
Unable to start activity
ComponentInfo{com.example.logintutorial/com.example.logintutorial.MainActivity}:
java.lang.NullPointerException: Argument 'applicationId' cannot be null

The metadata tag has to be inside the application tag and not outside!. Change manifest to:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.logintutorial"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="20" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="#string/app_id"/>
</application>
</manifest>

Related

clicked_branch_link always equals false

I'm having issues getting Branch to recognize that I clicked on a branch link.
I'm launching the following from hangouts: mens-essentials-mag.test-app.link
I get "+is_first_session":true
but always "+clicked_branch_link":false
Things I've done:
In my Activity:
#Override
protected void onStart() {
super.onStart();
Branch.getTestInstance(this).initSession(new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
Log.i("BRANCH SDK", referringParams.toString());
} else {
Log.i("BRANCH SDK", error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
In my Application:
// Branch logging for debugging
Branch.enableLogging();
// Branch object initialization
Branch.getAutoTestInstance(this);
In Manifest:
<!-- Branch App Links (optional) -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="mens-essentials-mag.test-app.link" />
<data android:scheme="https" android:host="mens-essentials-mag-alternate.test-app.link" />
</intent-filter>
and
<!-- Branch init -->
<meta-data android:name="io.branch.sdk.BranchKey" android:value="key_live_[myLiveKey]" />
<meta-data android:name="io.branch.sdk.BranchKey.test" android:value="key_test_[myTestKey]" />
<meta-data android:name="io.branch.sdk.TestMode" android:value="true" /> <!-- Set to true to use Branch_Test_Key -->
<!-- Branch install referrer tracking (optional) -->
<receiver android:name="io.branch.referral.InstallListener" android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
Also, I used this:
branch.initSessionTtl = 10000;
branch.subscribe(({error, params}) => {
if (error) {
console.log('Error from Branch: ' + error);
return;
} else if (params?.provider_group?.allow_patient_registration_app) {
this.setState({
showSignUp: params?.provider_group?.allow_patient_registration_app,
});
this.props.navigation.navigate('Signup');
}
console.log('Received link response from Branch', params);
});
let lastParams = await branch.getLatestReferringParams();
console.log('Received link response from Branch', lastParams);

Denied permission for ContentProvider

I have two applications that I want to send data between, lets call them ProviderApp and ResolverApp.
In the ProviderApp I have a Content Provider. Here is the AndroidManifest file for this app.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rewardreacher">
<permission android:name="android.permission.READ_GOALS"
/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher_reward"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_reward_round"
android:supportsRtl="true"
android:theme="#style/Theme.RewardReacher">
<provider
android:name=".provider.MyContentProvider"
android:authorities="com.example.rewardreacher.provider.MyContentProvider"
android:readPermission="android.permission.READ_GOALS"
android:multiprocess="true"
android:enabled="true"
android:exported="true">
</provider>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Now I am trying to access this data from ResolverApp using this AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.workouttracker">
<uses-permission android:name="android.permission.READ_GOALS"/>
<queries>
<provider android:authorities="com.example.rewardreacher.provider.MyContentProvider"/>
</queries>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher_workout"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_workout_round"
android:supportsRtl="true"
android:theme="#style/Theme.WorkoutTracker">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
And MainActivity
package com.example.workouttracker
import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentValues.TAG
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
private fun isStoragePermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED
) {
Log.v(TAG, "Permission is granted")
true
} else {
Log.v(TAG, "Permission is revoked")
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
1
)
false
}
} else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG, "Permission is granted")
true
}
}
#SuppressLint("Range")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "Permission: " + permissions[0] + "was " + grantResults[0])
val resultView = findViewById<View>(R.id.res) as TextView
val cursor = contentResolver.query(CONTENT_URI, null, null, null, null)
if (cursor!!.moveToFirst()) {
val strBuild = StringBuilder()
while (!cursor.isAfterLast) {
strBuild.append("""
${cursor.getString(cursor.getColumnIndex("id"))} - ${cursor.getString(cursor.getColumnIndex("name"))} - ${cursor.getString(cursor.getColumnIndex("frequency"))} - ${cursor.getString(cursor.getColumnIndex("performed"))}
""".trimIndent())
cursor.moveToNext()
}
resultView.text = strBuild
} else {
resultView.text = "No Records Found"
}
}
}
var CONTENT_URI = Uri.parse("content://com.example.rewardreacher.provider.MyContentProvider/goals")
#SuppressLint("Range")
fun onClickShowDetails(view: View?) {
// inserting complete table details in this text field
isStoragePermissionGranted()
// creating a cursor object of the
// content URI
}
}
But I keep getting "Permission is revoked" from the isStoragePermissionGranted()...
What am I doing wrong?

No adapter attached; skipping layout fragment Kotlin

This is my code and I can't see what is the problem. The error I keep seeing is No adapter attached; skipping layout.
AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gsixacademy.android.bikesrevisited">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.BikesRevisited"
android:usesCleartextTraffic="true"
>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
ADAPTER:
package com.gsixacademy.android.bikesrevisited.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gsixacademy.android.bikesrevisited.R
import com.gsixacademy.android.bikesrevisited.models.BikeModel
import com.gsixacademy.android.bikesrevisited.models.BikeResult
import kotlinx.android.synthetic.main.bikes_custom_row.view.*
class Adapter(
var bikeList: ArrayList<BikeResult>,
val bikesOnClickEvent:(BikesOnClickEvent)->Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.bikes_custom_row, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var myHolder = holder as MyViewHolder
myHolder.bindData(bikeList[position], position)
}
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindData(itemModel: BikeResult, position: Int) {
itemView.city_TV.text = itemModel.city
itemView.country_TV.text = itemModel.country
}
}
override fun getItemCount(): Int {
return bikeList.size
}
}
BIKES ON CLICK EVENT:
package com.gsixacademy.android.bikesrevisited.adapters
import com.gsixacademy.android.bikesrevisited.models.BikeResult
sealed class BikesOnClickEvent {
data class BikesClicked(val bike:BikeResult):BikesOnClickEvent()
}
APISERVICEBUILDER:
package com.gsixacademy.android.bikesrevisited.api
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ApiServiceBuilder {
val client=OkHttpClient.Builder().build()
val retrofit= Retrofit.Builder()
.baseUrl("http://api.citybik.es/v2/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <T>buildService(service:Class<T>):T{
return retrofit.create(service)
}
}
BIKESAPI:
package com.gsixacademy.android.bikesrevisited.api
import com.gsixacademy.android.bikesrevisited.models.BikeModel
import retrofit2.Call
import retrofit2.http.GET
interface BikesApi {
#GET("networks")
fun getBikes(): Call<BikeModel>
}
BIKE MODEL
package com.gsixacademy.android.bikesrevisited.models
class BikeModel (val networks:ArrayList<BikeResult> )
BIKE RESULT
package com.gsixacademy.android.bikesrevisited.models
class BikeResult (
val city:String,val country:String
)
BIKE FRAGMENT
package com.gsixacademy.android.bikesrevisited
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.gsixacademy.android.bikesrevisited.R
import kotlinx.android.synthetic.main.recycler_view.*
import androidx.recyclerview.widget.LinearLayoutManager
import com.gsixacademy.android.bikesrevisited.adapters.Adapter
import com.gsixacademy.android.bikesrevisited.api.ApiServiceBuilder
import com.gsixacademy.android.bikesrevisited.api.BikesApi
import com.gsixacademy.android.bikesrevisited.models.BikeModel
import com.gsixacademy.android.bikesrevisited.models.BikeResult
import kotlinx.android.synthetic.main.recycler_view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class BikeFragment:Fragment() {
var request=ApiServiceBuilder.buildService(BikesApi::class.java)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view=inflater.inflate(R.layout.recycler_view,container,false)
val call=request.getBikes()
call.enqueue(object :Callback<BikeModel>{
override fun onResponse(call: Call<BikeModel>, response: Response<BikeModel>) {
if (response.isSuccessful){
var bikeResponse=response.body()
var bikeList=bikeResponse?.networks
if (bikeList!=null){
var bikelistAdapter=Adapter(bikeList){
}
recycler_view.layoutManager=LinearLayoutManager(context)
recycler_view.adapter=bikelistAdapter
}
}
else{}
}
override fun onFailure(call: Call<BikeModel>, t: Throwable) {
Toast.makeText(activity,t.message, Toast.LENGTH_SHORT)
.show()
}
})
return view
}
}
MAIN ACTIVITY:
package com.gsixacademy.android.bikesrevisited
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
SPLASH FRAGMENT:
package com.gsixacademy.android.bikesrevisited
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class SplashFragment:Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.splash,container,false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
GlobalScope.launch {
Thread.sleep(1500)
findNavController().navigate(R.id.action_splashFragment_to_bikeFragment)
}
}
}
layouts
activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">
<fragment
android:id="#+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:navGraph="#navigation/navigation_graph"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
bikes_custom_row
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="#+id/card_view_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="10dp"
app:cardCornerRadius="10dp"
app:cardElevation="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
tools:ignore="UseCompoindDrawables">
<TextView
android:id="#+id/city_TV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="city"
android:textColor="#color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="#+id/country_TV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="country"
android:textColor="#color/black"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
recycler_view
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
splash
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imageView_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/ic_launcher_background"
android:scaleType="centerInside"/>
</androidx.constraintlayout.widget.ConstraintLayout>
navigation_graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation_graph"
app:startDestination="#id/splashFragment">
<fragment
android:id="#+id/splashFragment"
android:name="com.gsixacademy.android.bikesrevisited.SplashFragment"
android:label="SplashFragment" >
<action
android:id="#+id/action_splashFragment_to_bikeFragment"
app:destination="#id/bikeFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/bikeFragment"
android:name="com.gsixacademy.android.bikesrevisited.BikeFragment"
android:label="BikeFragment" />
</navigation>

Exoplayer with Viewpager2 and recycler view adapter

I have recently discovered abut Viewpager 2 and exoplayer and not sure how to integrate them both. Viewpager contains a list with video player and each takes up the full screen. You can swipe to move to the next video. Can anybody help me with a simple implementation of Exoplayer with Viewpager2
Yes I can help you.
It is a very simple implementation of viewpager2 with ExoPlayer.
It will show you online videos and you can swipe these videos horizontally.
Lets discuss it step by step:
Step 1:
Add internet permission in your Manifest file:
<uses-permission android:name="android.permission.INTERNET"/>
Step 2:
Add ExoPlayer dependency to your build.gradle
// exo player
implementation 'com.google.android.exoplayer:exoplayer:2.17.1'
Step 3 :
Add the following code to your themes.xml
<style name="ClickableView">
<item name="colorControlHighlight">#android:color/darker_gray</item>
<item name="android:background">?selectableItemBackgroundBorderless</item>
</style>
Step 4:
Create a controller layout file to control ExoPlayer named as custom_controller.xml. And then paste bellow code in that file.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#80000000"
android:layout_height="match_parent">
<ImageView
android:id="#+id/exo_lock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:padding="10dp"
android:src="#drawable/ic_baseline_lock_open"
android:theme="#style/ClickableView" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sec_controlvid1"
android:orientation="horizontal"
android:layout_centerInParent="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/exo_rew"
android:src="#drawable/ic_baseline_replay"
android:layout_marginLeft="30dp"
android:theme="#style/ClickableView"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/exo_play"
android:src="#drawable/ic_baseline_play_arrow"
android:layout_marginHorizontal="30dp"
android:theme="#style/ClickableView"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/exo_pause"
android:src="#drawable/ic_baseline_pause"
android:layout_marginHorizontal="30dp"
android:theme="#style/ClickableView"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/exo_ffwd"
android:layout_marginRight="30dp"
android:src="#drawable/ic_baseline_forward"
android:theme="#style/ClickableView"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/sec_controlvid2"
android:orientation="vertical"
android:padding="8dp"
android:gravity="center"
android:layout_alignParentBottom="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
android:gravity="center"
android:layout_alignParentBottom="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/exo_position"
android:textColor="#color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
android:layout_marginHorizontal="4dp"
android:textColor="#CBCDC8"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/exo_duration"
android:layout_weight="1"
android:textColor="#CBCDC8"/>
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="#drawable/ic_baseline_fullscreen"
android:id="#+id/bt_fullscreen"
android:theme="#style/ClickableView"/>
</LinearLayout>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/exo_progress"
app:unplayed_color="#42454E"
app:buffered_color="#95989F"
app:played_color="#FF0000"
app:scrubber_color="#FF0000"
android:layout_alignParentBottom="true"
android:layout_marginTop="-8dp"/>
</LinearLayout>
</RelativeLayout>
Step 5:
Create a layout file for representing items in viewpager named as video_card_item.xml and write the following code in it:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_vertical"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.exoplayer2.ui.PlayerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:id="#+id/statusSliderVideo"
app:use_controller="true"
android:background="#android:color/background_dark"
app:player_layout_id="#layout/exo_player_view"
app:controller_layout_id="#layout/custom_controller"/>
<ImageView
android:id="#+id/statusSliderThumbnailImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="#drawable/ic_default_image" />
<ImageView
android:id="#+id/playPauseBtn"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:src="#drawable/ic_play"
app:tint="#color/white" />
<ProgressBar
android:layout_width="80dp"
android:layout_height="80dp"
android:visibility="gone"
android:id="#+id/progress_bar"
android:layout_centerInParent="true"/>
</RelativeLayout>
Step 6 :
Create adapter class for viewpager items named as VideoSliderAdapter.java and write bellow code in it:
package com.example.practiceproject;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ui.PlayerView;
import java.util.ArrayList;
public class VideoSliderAdapter extends RecyclerView.Adapter<VideoSliderAdapter.ViewHolder> {
private Context context;
private ArrayList<String> pathsList;
private AppCompatActivity activity;
private boolean isFullScreen = false;
public static boolean isFullLock = false;
public VideoSliderAdapter(Context context, ArrayList<String> pathsList, AppCompatActivity activity) {
this.context = context;
this.pathsList = pathsList;
this.activity = activity;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.video_card_item, parent, false);
return new ViewHolder(view);
}
#SuppressLint("ClickableViewAccessibility")
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.thumbnailImage.setImageBitmap(ThumbnailUtils.createVideoThumbnail(pathsList.get(position),
MediaStore.Video.Thumbnails.FULL_SCREEN_KIND));
// get data
Uri videoUri = Uri.parse(pathsList.get(position));
holder.bt_fullscreen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!isFullScreen) {
holder.bt_fullscreen.setImageDrawable(
ContextCompat.getDrawable(context, R.drawable.ic_baseline_fullscreen_exit)
);
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
} else {
holder.bt_fullscreen.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_baseline_fullscreen));
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
isFullScreen = !isFullScreen;
}
});
holder.bt_lockscreen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (isFullLock) {
holder.bt_lockscreen.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_baseline_lock_open));
} else {
holder.bt_lockscreen.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_baseline_lock));
}
isFullLock = !isFullLock;
lockScreen(isFullLock, holder.sec_mid, holder.sec_bottom);
}
});
holder.playPauseBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
holder.playPauseBtn.setVisibility(View.GONE);
// holder.thumbnailImage.setVisibility(View.GONE);
holder.playerView.setVisibility(View.VISIBLE);
holder.simpleExoPlayer = new SimpleExoPlayer.Builder(context)
.setSeekBackIncrementMs(5000)
.setSeekForwardIncrementMs(5000)
.build();
holder.playerView.setPlayer(holder.simpleExoPlayer);
holder.playerView.setKeepScreenOn(true);
holder.simpleExoPlayer.addListener(new Player.Listener() {
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_BUFFERING) {
holder.progressBar.setVisibility(View.VISIBLE);
} else if (playbackState == Player.STATE_READY) {
holder.progressBar.setVisibility(View.GONE);
}
}
});
MediaItem mediaItem = MediaItem.fromUri(videoUri);
holder.simpleExoPlayer.setMediaItem(mediaItem);
holder.simpleExoPlayer.prepare();
holder.simpleExoPlayer.play();
}
});
}
private void lockScreen(boolean isFullLock, LinearLayout sec_mid, LinearLayout sec_bottom) {
if (isFullLock) {
sec_mid.setVisibility(View.INVISIBLE);
sec_bottom.setVisibility(View.INVISIBLE);
} else {
sec_mid.setVisibility(View.VISIBLE);
sec_bottom.setVisibility(View.VISIBLE);
}
}
#Override
public int getItemCount() {
return pathsList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
PlayerView playerView;
ImageView thumbnailImage;
ImageView playPauseBtn;
ImageView bt_fullscreen, bt_lockscreen;
SimpleExoPlayer simpleExoPlayer;
ProgressBar progressBar;
LinearLayout sec_mid, sec_bottom;
public ViewHolder(#NonNull View view) {
super(view);
playerView = view.findViewById(R.id.statusSliderVideo);
thumbnailImage = view.findViewById(R.id.statusSliderThumbnailImage);
playPauseBtn = view.findViewById(R.id.playPauseBtn);
progressBar = view.findViewById(R.id.progress_bar);
bt_fullscreen = view.findViewById(R.id.bt_fullscreen);
bt_lockscreen = view.findViewById(R.id.exo_lock);
sec_mid = view.findViewById(R.id.sec_controlvid1);
sec_bottom = view.findViewById(R.id.sec_controlvid2);
}
}
}
Step 7 :
Paste following code in your activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clipToPadding="true"
android:foregroundGravity="center"
android:overScrollMode="never"
android:id="#+id/videoViewPager"/>
</LinearLayout>
Step 8 :
Paste bellow code in your MainActivity.java:
package com.example.practiceproject;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import android.os.Bundle;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ViewPager2 viewPager;
private VideoSliderAdapter myAdapter;
ArrayList<String> videoPaths;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = findViewById(R.id.videoViewPager);
videoPaths = new ArrayList<>();
videoPaths.add("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4");
videoPaths.add("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4");
videoPaths.add("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4");
videoPaths.add("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4");
videoPaths.add("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4");
// add paths for video simllarly
myAdapter = new VideoSliderAdapter(getApplicationContext(), videoPaths,
MainActivity.this);
viewPager.setAdapter(myAdapter);
viewPager.setPadding(10, 0, 10, 0);
}
}
Completed
Now you are good to go. Enjoy the app.. Best of Luck.
If you want to get project files (Source Code) for more simplicity, you can get source code from my github profile.
link : https://github.com/AbdullahProgrammer426351/Custom-Video-Slider-with-Exo-Player-and-ViewPager2

Data binding: Not call onClick method

Android 4.3+
I trt to use data binding. I use official documentation from here Data binding
So in app/build.gradle:
dataBinding {
enabled = true
}
In my xml layout file:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="handler"
type="com.myproject.SettingsFragment" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:id="#+id/contactUsContainer"
android:layout_width="match_parent"
android:onClick="#{handler::onClickContactUs}">
<TextView
android:id="#+id/contactUsTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</FrameLayout>
</ScrollView>
</android.support.constraint.ConstraintLayout>
</layout>
And here my fragment SettingsFragment.java:
public class SettingsFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.settings, container, false);
return rootView;
}
public void onClickContactUs(View view) {
}
}
But when I click on container contactUsContainer the method onClickContactUs() is NOT call.
Why?
You're experiencing a common problem. First, you must inflate the binding using the binding inflate() call. Second, you must set the binding variables:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
MyLayoutBinding binding = MyLayoutBinding.inflate(inflater, container, false);
binding.setHandler(this);
return binding.getRoot();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
userDetailViewBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_user_detail_view, container, false);
ProfileViewModel.Factory factory = new ProfileViewModel.Factory(getActivity().getApplication());
viewModel = ViewModelProviders.of(this, factory)
.get(ProfileViewModel.class);
observeViewModel(viewModel);
userDetailViewBinding.setProfileViewModel(viewModel);
userDetailViewBinding.setUserDetailViewFrag(this);
return userDetailViewBinding.getRoot();
}