我开始开发一个 100% 使用 Jetpack compose 编写的新项目,这意味着我们没有任何片段,并且我们也遵循单一活动模式。
现在我必须实现 Facebook 登录,但我陷入困境,因为他们仍在使用已弃用的 onActivityResult 而不是新的合约 api。
这是我正在尝试follow的文档,任何帮助将不胜感激。
谢谢大家,
您必须等待此问题得到解决。
现在,您可以使用
callbackManager
将 CompositionLocalProvider
从您的 Activity 传递到 Compose 树,如下所示:
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
}
更通用的解决方案是将 facebook 逻辑移至视图模式,然后传递,然后你必须创建自己的回调管理器,如下所示:
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")
}
}
}
您也可以尝试构建这个分叉,它包含来自这个拉取请求的更改。它增加了合约API的支持,但尚未被接受。仔细检查更改,这不是官方的!
试试这个,它会起作用:
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")
}
})
对我有用的另一种方法是将 FB 登录逻辑封装在一个没有 UI 的空白 Activity 中,并在 Facebook 启动后立即启动。一旦您在活动中得到结果,就将它们传回步骤 2 中的 compose 函数。
第1步:创建FB登录活动
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)
}
}
}
第 2 步:现在只需在您的撰写函数中使用
facebookSignRequest.launch(FBLoginActivity.getInstance(context))
调用此函数,如下所示。
@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))
)
}
}
第 3 步:不要忘记将活动添加到清单中😀
我们不再需要使用旧的已弃用的方式,即onActivityResult。专为 Jetpack Compose 爱好者打造。
@Composable
fun FacebookLogInScreen(
modifier: Modifier = Modifier
) {
val callbackManager = remember {
CallbackManager.Factory.create()
}
val fbLauncher = rememberLauncherForActivityResult(
LoginManager.getInstance().createLogInActivityResultContract(callbackManager)
) { result ->
LoginManager.getInstance().onActivityResult(
result.resultCode,
result.data,
object: FacebookCallback<LoginResult> {
override fun onSuccess(result: LoginResult) {
println("onSuccess $result")
}
override fun onCancel() {
println("onCancel")
}
override fun onError(error: FacebookException) {
println("onError $error")
}
}
)
}
Column(
modifier = modifier
) {
Button(onClick = {
fbLauncher.launch(listOf("email", "business_management"))
//"business_management", "read_insights", "pages_read_engagement", "pages_show_list"
}) {
Text(text = "Login")
}
}
}