如何解决错误“LifecycleOwners必须在启动之前调用register”

问题描述 投票:0回答:9

我在开发中使用

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)

        }

    }
android kotlin android-fragments
9个回答
108
投票

对我来说,问题是我在

registerForActivityResult
中调用
onClickListener
,仅在单击按钮时调用(此时应用程序处于“RESUMED”状态)。将调用移至按钮的
onClickListener
之外并移至 Activity 的
onCreate
方法中即可修复此问题。


48
投票

引自文档

registerForActivityResult() 在创建片段或活动之前可以安全地调用,从而允许在为返回的 ActivityResultLauncher 实例声明成员变量时直接使用它。 注意:虽然在创建片段或活动之前调用 registerForActivityResult() 是安全的,但在片段或活动的生命周期达到 CREATED 之前,您无法启动 ActivityResultLauncher。

因此,要解决您的问题,请将您的注册调用移至

onCreate()
之外,并将其放入
fragment
范围内,然后在 google_sign_in_button
click-listener
上调用启动函数

注意:如果您使用 Kotlin-Android-Extention,请将

click-listener
调用移至
onViewCreated()


36
投票

如果您使用的是 Fragment,请确保您没有在活动中执行

registerForActivityResult
。片段还有一个
registerForActivityResult
,这就是您应该使用的。


9
投票

您必须从

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


8
投票

我找到了一个可以在任何地方使用的解决方案。

查看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()

更多信息:在单独的班级中接收活动结果


6
投票

registerForActivityResult() 在创建片段或活动之前可以安全地调用,允许在为返回的 ActivityResultLauncher 实例声明成员变量时直接使用它。

您应该在创建视图之前调用registerForActivityResult。成员变量或onCreate()


0
投票

发现同样的问题并设法开始使用一些魔法。

就我而言,它发生在

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
内完成的,而不是在恢复步骤中完成的。


0
投票

如果您正在使用任何第三方库,那么您可能无法在代码中看到“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();
            }
        });

就是这样!


0
投票

如果您使用 Jetpack Compose,则可以使用 rememberLauncherForActivityResult。这样就无需在

onCreate
中手动创建对启动器的引用,并在应用程序的其他位置提供对其的访问。

@Composable
internal fun ExampleActivityLauncher() {
    val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.YOUR_CONTRACT) {
      // handle result
    }

    Button(onClick = {
      launcher.launch(/* ... */)
    }) { Text("Launch Activity") }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.