skip to Main Content

I started working on a new project that is 100% written with Jetpack compose, meaning we don’t have any fragments and we’re also following the Single activity pattern.

Now I have to implement the Facebook login but I’m stuck since they’re still using the deprecated onActivityResult instead of the new contract api.

Here’s the documentation that I’m trying to follow, any help would be greatly appreciated.

Thank you all,

3

Answers


  1. You have to wait this issue to be resolved.

    For now you can pass callbackManager down from your activity to the Compose tree using CompositionLocalProvider, like this:

    val LocalFacebookCallbackManager =
        staticCompositionLocalOf<CallbackManager> { error("No CallbackManager provided") }
    
    class MainActivity : FragmentActivity() {
        private var callbackManager = CallbackManager.Factory.create();
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                Theme {
                    CompositionLocalProvider(
                        LocalFacebookCallbackManager provides callbackManager
                    ) {
                        LoginScreen()
                    }
                }
            }
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            callbackManager.onActivityResult(requestCode, resultCode, data)
            super.onActivityResult(requestCode, resultCode, data)
        }
    }
    
    @Composable
    fun LoginScreen() {
        val callbackManager = LocalFacebookCallbackManager.current
        DisposableEffect(Unit) {
            LoginManager.getInstance().registerCallback(
                callbackManager,
                object : FacebookCallback<LoginResult> {
                    override fun onSuccess(loginResult: LoginResult) {
                        println("onSuccess $loginResult")
                    }
    
                    override fun onCancel() {
                        println("onCancel")
                    }
    
                    override fun onError(exception: FacebookException) {
                        println("onError $exception")
                    }
                }
            )
            onDispose {
                LoginManager.getInstance().unregisterCallback(callbackManager)
            }
        }
        val context = LocalContext.current
        Button(onClick = {
            LoginManager.getInstance()
                .logInWithReadPermissions(context.findActivity(), Arrays.asList("public_profile"));
        }) {
            Text("FB Login")
        }
    }
    
    fun Context.findActivity(): Activity? = when (this) {
        is Activity -> this
        is ContextWrapper -> baseContext.findActivity()
        else -> null
    }
    

    More general solution is moving facebook logic into a view mode, and passing, then you have to create your own callback manager, something like this:

    ActivityResultCallbackManager.kt

    val LocalActivityResultCallbackManager =
        staticCompositionLocalOf<ActivityResultCallbackManager> { error("No ActivityResultCallbackManager provided") }
    
    interface ActivityResultCallbackI {
        fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean
    }
    
    class ActivityResultCallbackManager {
    
        private val listeners = mutableListOf<ActivityResultCallbackI>()
    
        fun addListener(listener : ActivityResultCallbackI) {
            listeners.add(listener)
        }
    
        fun removeListener(listener : ActivityResultCallbackI) {
            listeners.remove(listener)
        }
    
        fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) : Boolean =
            listeners.any { it.onActivityResult(requestCode, resultCode, data) }
    }
    

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
        private var callbackManager = ActivityResultCallbackManager()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            WindowCompat.setDecorFitsSystemWindows(window, false)
            setContent {
                Theme {
                    CompositionLocalProvider(
                        LocalActivityResultCallbackManager provides callbackManager
                    ) {
                        LoginScreen()
                    }
                }
            }
        }
    
        override fun onActivityResult(
            requestCode: Int,
            resultCode: Int,
            data: Intent?
        ) {
            if (!callbackManager.onActivityResult(requestCode, resultCode, data)) {
                super.onActivityResult(requestCode, resultCode, data)
            }
        }
    }
    

    FacebookLoginViewModel.kt

    class FacebookLoginViewModel : ViewModel(), ActivityResultCallbackI {
        sealed class LoginState {
            object Initial: LoginState()
            object Processing: LoginState()
            data class Success(val loginResult: LoginResult): LoginState()
            data class Error(val exception: FacebookException): LoginState()
        }
    
        private var callbackManager = CallbackManager.Factory.create()
        var state by mutableStateOf<LoginState>(LoginState.Initial)
            private set
    
        init {
            LoginManager.getInstance().registerCallback(
                callbackManager,
                object : FacebookCallback<LoginResult> {
                    override fun onSuccess(loginResult: LoginResult) {
                        state = LoginState.Success(loginResult)
                    }
    
                    override fun onCancel() {
                        state = LoginState.Initial
                    }
    
                    override fun onError(exception: FacebookException) {
                        state = LoginState.Error(exception)
                    }
                }
            )
        }
    
        override fun onCleared() {
            super.onCleared()
            LoginManager.getInstance().unregisterCallback(callbackManager)
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean =
            callbackManager.onActivityResult(requestCode, resultCode, data)
    
        fun login(context: Context) {
            state = LoginState.Processing
            LoginManager.getInstance()
                .logInWithReadPermissions(context.findActivity(), Arrays.asList("public_profile"));
        }
    }
    

    LoginScreen.kt

    @Composable
    fun LoginScreen() {
        val viewModel: FacebookLoginViewModel = viewModel()
        val callbackManager = LocalActivityResultCallbackManager.current
        DisposableEffect(Unit) {
            callbackManager.addListener(viewModel)
            onDispose {
                callbackManager.removeListener(viewModel)
            }
        }
        val context = LocalContext.current
        Column {
            Text(viewModel.state.toString())
            Button(onClick = {
                viewModel.login(context)
            }) {
                Text("FB Login")
            }
        }
    }
    

    Also you can try building this fork, it contains changes from this pull request. It adds support of contract api, and is not yet accepted. Check out changes carefully, it’s not official!

    Login or Signup to reply.
  2. Try this its will work:

    val callbackManager = CallbackManager.Factory.create()
    val loginManager = LoginManager.getInstance()
    val context = LocalContext.current
    
     loginManager.logIn(
         context as ActivityResultRegistryOwner,
         callbackManager,
         listOf("email")
     )
    
     loginManager.registerCallback(
         callbackManager,
         object : FacebookCallback<LoginResult?> {
             override fun onCancel() {
                 TODO("onCancel")
             }
    
             override fun onError(error: FacebookException) {
                 TODO("onError")
             }
    
             override fun onSuccess(result: LoginResult?) {
                 TODO("onSuccess")
             }
       })
    
    Login or Signup to reply.
  3. Another approach that worked for me is to encapsulate the FB login logic in a blank activity with no UI and start facebook login as soon as it starts. As soon as you get back the results in your activity pass them back to compose function back in step 2.

    Step 1: Create FB Login Activity

    package com.login.view.login
    
    import android.content.Context
    import android.content.Intent
    import android.os.Bundle
    import android.util.Log
    import androidx.activity.ComponentActivity
    import com.facebook.CallbackManager
    import com.facebook.FacebookCallback
    import com.facebook.FacebookException
    import com.facebook.login.LoginManager
    import com.facebook.login.LoginResult
    
    class FBLoginActivity : ComponentActivity() {
    
        private val callbackManager: CallbackManager by lazy {
            CallbackManager.Factory.create()
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setupLogin()
            doLogin()
    
        }
    
        private fun doLogin() {
            LoginManager.getInstance().logInWithReadPermissions(
                this,
                listOf("user_photos", "email", "public_profile", "AccessToken")
            )
        }
    
        private fun setupLogin() {
            LoginManager.getInstance()
                .registerCallback(callbackManager, callback = object : FacebookCallback<LoginResult> {
                    override fun onCancel() {
                        Log.d("TAG++", "facebook login cancelled")
                        setResult(RESULT_CANCELED)
                        finish()
                    }
    
                    override fun onError(error: FacebookException) {
                        Log.d("TAG++", "facebook login error")
                        setResult(RESULT_ERROR)
                        finish()
                    }
    
                    override fun onSuccess(result: LoginResult) {
                        Log.d("TAG++", "facebook login success")
                        val intent = Intent().apply {
                            putExtra(EXTRA_DATA_FB, result.accessToken)
                        }
                        setResult(RESULT_OK, intent)
                        finish()
                    }
                })
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            callbackManager.onActivityResult(requestCode, resultCode, data)
            super.onActivityResult(requestCode, resultCode, data)
        }
    
        companion object {
    
            const val RESULT_ERROR = 102
            const val EXTRA_DATA_FB = "extraDataFb"
    
            fun getInstance(context: Context): Intent {
                return Intent(context, FBLoginActivity::class.java)
            }
        }
    }
    

    Step 2: Now just call this with facebookSignRequest.launch(FBLoginActivity.getInstance(context)) from within your compose function like below.

    @Composable
    fun LoginViewComponent() {
        val context = LocalContext.current
    
        val facebookSignRequest =
            rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
                if (result.resultCode == Activity.RESULT_OK && result.data != null) {
                    val data = result.data?.getStringExtra(FBLoginActivity.EXTRA_DATA_FB)
                    //do something with data 
                }
            }
    
        OutlinedButton(
            onClick = {
                facebookSignRequest.launch(FBLoginActivity.getInstance(context))
            },
            shape = RoundedCornerShape(20),
            border = BorderStroke(1.dp, color = Color.White),
            modifier = Modifier
                .padding(8.dp)
                .height(60.dp)
        ) {
            Image(
                painter = painterResource(id = R.drawable.ic_fb),
                contentDescription = "facebook login",
                colorFilter = ColorFilter.tint(color = textPrimary.copy(alpha = .7f))
            )
        }
    }
    

    Step 3: don’t forget to add activity to manifest 😀

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search