我在开发中使用
registerForActivityResult
进行谷歌登录实现。一切都工作正常,直到我将片段依赖项升级到 1.3.0-beta01。应用程序当前崩溃并出现错误
java.lang.IllegalStateException:LifecycleOwner SignupChoicesFragment{8e0e269} (193105b9-afe2-4941-a368-266dbc433258) id=0x7f090139} 正在尝试在当前状态恢复时注册。 LifecycleOwners 在开始之前必须调用 register。
我在 oncreate 之前使用过该函数,使用延迟加载,但它仍然无法工作。
class SignupChoicesFragment : DaggerFragment() {
@Inject
lateinit var viewModelProviderFactory: ViewModelFactory
val userViewModel: UserViewModel by lazy {
ViewModelProvider(this, viewModelProviderFactory).get(UserViewModel::class.java)
}
@Inject
lateinit var mGoogleSignInClient:GoogleSignInClient
val arg:SignupChoicesFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_signup_choices, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
google_sign_in_button.setOnClickListener {
val intent = mGoogleSignInClient.signInIntent
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult(), ActivityResultCallback {result->
if (result.resultCode == Activity.RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
task.addOnCompleteListener {
if (it.isSuccessful) {
val account: GoogleSignInAccount? =
it.getResult(ApiException::class.java)
val idToken = it.result?.idToken
val email = account?.email
val lastName = account?.familyName
val firstName = account?.givenName
val otherName = account?.displayName
val imageUrl = account?.photoUrl
val category = arg.category
val newUser = User()
newUser.firstName = firstName
newUser.lastName = lastName
newUser.otherName = otherName
newUser.category = category
newUser.email = email
newUser.imageUrl = imageUrl.toString()
userViewModel.currentUser = newUser
newUser.token = idToken
i(title, "idToken $idToken")
requireActivity().gdToast("Authentication successful", Gravity.BOTTOM)
val action = SignupChoicesFragmentDirections.actionSignupChoicesFragmentToEmailSignupFragment()
action.newUser = newUser
goto(action)
} else {
requireActivity().gdToast(
"Authentication Unsuccessful",
Gravity.BOTTOM
)
Log.i(title, "Task not successful")
}
}
} else {
Log.i(title, "OKCODE ${Activity.RESULT_OK} RESULTCODE ${result.resultCode}")
}
}).launch(intent)
}
}
对我来说,问题是我在
registerForActivityResult
中调用 onClickListener
,仅在单击按钮时调用(此时应用程序处于“RESUMED”状态)。将调用移至按钮的 onClickListener
之外并移至 Activity 的 onCreate
方法中即可修复此问题。
引自文档
registerForActivityResult() 在创建片段或活动之前可以安全地调用,从而允许在为返回的 ActivityResultLauncher 实例声明成员变量时直接使用它。 注意:虽然在创建片段或活动之前调用 registerForActivityResult() 是安全的,但在片段或活动的生命周期达到 CREATED 之前,您无法启动 ActivityResultLauncher。
因此,要解决您的问题,请将您的注册调用移至
onCreate()
之外,并将其放入 fragment
范围内,然后在 google_sign_in_button click-listener
上调用启动函数
注意:如果您使用 Kotlin-Android-Extention,请将
click-listener
调用移至 onViewCreated()
如果您使用的是 Fragment,请确保您没有在活动中执行
registerForActivityResult
。片段还有一个 registerForActivityResult
,这就是您应该使用的。
您必须从
val launcher = registerForActivityResult...
中删除 setOnClickListener
,然后将其保存在变量中,在您的示例中是 launcher
并在 setOnClickListener
中使用 .launch 执行变量,在您的示例中是 es launcher
.
你的代码看起来像这样
google_sign_in_button.setOnClickListener {
val intent = mGoogleSignInClient.signInIntent
launcher.launch(intent)
}
private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult(), ActivityResultCallback {result->
if (result.resultCode == Activity.RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
task.addOnCompleteListener {
if (it.isSuccessful) {
val account: GoogleSignInAccount? =
it.getResult(ApiException::class.java)
val idToken = it.result?.idToken
val email = account?.email
val lastName = account?.familyName
val firstName = account?.givenName
val otherName = account?.displayName
val imageUrl = account?.photoUrl
val category = arg.category
val newUser = User()
newUser.firstName = firstName
newUser.lastName = lastName
newUser.otherName = otherName
newUser.category = category
newUser.email = email
newUser.imageUrl = imageUrl.toString()
userViewModel.currentUser = newUser
newUser.token = idToken
i(title, "idToken $idToken")
requireActivity().gdToast("Authentication successful", Gravity.BOTTOM)
val action = SignupChoicesFragmentDirections.actionSignupChoicesFragmentToEmailSignupFragment()
action.newUser = newUser
goto(action)
} else {
requireActivity().gdToast(
"Authentication Unsuccessful",
Gravity.BOTTOM
)
Log.i(title, "Task not successful")
}
}
} else {
Log.i(title, "OKCODE ${Activity.RESULT_OK} RESULTCODE ${result.resultCode}")
}
})
来源:https://medium.com/codex/android-runtime-permissions-using-registerforactivityresult-68c4eb3c0b61
我找到了一个可以在任何地方使用的解决方案。
查看ActivityResultRegistry源码,可以看到有两个register函数,只有其中一个抛出if
lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)
。另一个不关心生命周期,但你必须自己调用unregister
。
调用此方法时,当不再需要启动器释放可能在注册回调中捕获的任何值时,您必须在返回的 ActivityResultLauncher 上调用 ActivityResultLauncher.unregister() 。
这是我想出的扩展函数:
fun <I, O> ComponentActivity.registerActivityResultLauncher(
contract: ActivityResultContract<I, O>,
callback: ActivityResultCallback<O>
): ActivityResultLauncher<I> {
val key = UUID.randomUUID().toString()
return activityResultRegistry.register(key, contract, callback)
}
像这样使用它:
val launcher = activity.registerActivityResultLauncher(
contract = ActivityResultContracts.StartActivityForResult(),
callback = { result ->
//do something with the result
}
)
完成后不要忘记取消注册:
launcher.unregister()
更多信息:在单独的班级中接收活动结果
registerForActivityResult() 在创建片段或活动之前可以安全地调用,允许在为返回的 ActivityResultLauncher 实例声明成员变量时直接使用它。
您应该在创建视图之前调用registerForActivityResult。成员变量或onCreate()
发现同样的问题并设法开始使用一些魔法。
就我而言,它发生在
Activity
,所以我是这样处理的:
//...other bits
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// doing the setup here
setupViews()
}
private fun setupViews() {
val buttonLauncher = navigator.gotoScreenForResult(this) { success ->
if (success) {
setResult(Activity.RESULT_OK)
finish()
}
}
binding.myButton.setOnClickListener {
buttonLauncher.launch(Unit)
}
其中
navigator.gotoScreenForResult
如下所示:
override fun gotoScreenForResult(context: AppCompatActivity, callback: (Boolean) -> Unit): ActivityResultLauncher<Unit> {
val contract = object : ActivityResultContract<Unit, Boolean>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context, MyNextActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
return resultCode == Activity.RESULT_OK
}
}
return context.registerForActivityResult(contract) { callback(it) }
}
只需确保
setupViews
是在 onCreate
内完成的,而不是在恢复步骤中完成的。
如果您正在使用任何第三方库,那么您可能无法在代码中看到“registerForActivityResult”,但它应该存在于同一库提供的类中。 因此,在这种情况下,我建议将与该库相关的行从任何侦听器移至 onCreate 方法。
例如-
btnBackup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final RoomBackup roomBackup = new RoomBackup(GoogleDriveActivity.this);
roomBackup.database(LocalDataBase.getInstance(getApplicationContext()));
roomBackup.enableLogDebug(true);
roomBackup.backupIsEncrypted(false);
roomBackup.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_INTERNAL);
roomBackup.onCompleteListener((success, message, exitCode) -> {
Log.d(TAG, "success: " + success + ", message: " + message + ", exitCode: " + exitCode);
if (success) roomBackup.restartApp(new Intent(getApplicationContext(), GoogleDriveActivity.class));
});
roomBackup.restore();
}
});
//// 从监听器中删除其他代码并转移到 onCreate
roomBackup = new RoomBackup(GoogleDriveActivity.this);
roomBackup.database(LocalDataBase.getInstance(getApplicationContext()));
roomBackup.enableLogDebug(true);
roomBackup.backupIsEncrypted(false);
roomBackup.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_INTERNAL);
roomBackup.maxFileCount(5);
roomBackup.onCompleteListener((success, message, exitCode) -> {
Log.d(TAG, "success: " + success + ", message: " + message + ", exitCode: " + exitCode);
if (success) roomBackup.restartApp(new Intent(getApplicationContext(), GoogleDriveActivity.class));
});
/// 您可以在侦听器中仅保留所需的行
btnBackup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
roomBackup.backup();
}
});
就是这样!
如果您使用 Jetpack Compose,则可以使用 rememberLauncherForActivityResult。这样就无需在
onCreate
中手动创建对启动器的引用,并在应用程序的其他位置提供对其的访问。
@Composable
internal fun ExampleActivityLauncher() {
val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.YOUR_CONTRACT) {
// handle result
}
Button(onClick = {
launcher.launch(/* ... */)
}) { Text("Launch Activity") }
}
}