集成手机号验证后Fragment未附加上下文异常排查
问题:集成Firebase手机号验证后Fragment跳转出现「未附加到上下文」错误
原本从MainActivity跳转至MoreFragment功能正常,集成Firebase手机号验证后抛出以下错误:
java.lang.IllegalStateException: Fragment MoreFragment{f2ca588} (0a0f4eb7-5df8-4d00-aa6d-18838fd9f570) not attached to a context.
相关代码
package com.example.ecommerce.fragments import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.example.ecommerce.RegisterAct import com.example.ecommerce.databinding.FragmentMoreBinding import com.google.firebase.FirebaseException import com.google.firebase.auth.* import com.google.firebase.ktx.Firebase import java.util.concurrent.TimeUnit class MoreFragment : Fragment() { private lateinit var binding: FragmentMoreBinding private val builder= AlertDialog.Builder(requireContext()).setTitle("Loading.....").setMessage("Please wait").setCancelable(false).create() private var activityContext: Context? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val context = activityContext ?: return // Use context here } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } @SuppressLint("SuspiciousIndentation") override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding=FragmentMoreBinding.inflate(layoutInflater) if(FirebaseAuth.getInstance().currentUser!=null) { binding.login.visibility=View.GONE binding.textView5.visibility=View.GONE return view } binding.login.setOnClickListener { binding.constraint.visibility=View.VISIBLE binding.cont.setOnClickListener { auth() } } binding.remove.setOnClickListener { binding.constraint.visibility=View.GONE } binding.register.setOnClickListener{ val intent=Intent(requireContext(),RegisterAct::class.java) startActivity(intent) } return binding.root } override fun onAttach(context: Context) { super.onAttach(context) activityContext = context } override fun onDetach() { super.onDetach() } private fun auth() { if(binding.Phonenumber.text.isEmpty() || binding.editTextPhone.text.isEmpty()) { Toast.makeText(requireContext(), "Please fill everything", Toast.LENGTH_SHORT).show() } else if (binding.editTextPhone.text.toString().trim().length != 2 ) { Toast.makeText(requireContext(), "Invalid country code", Toast.LENGTH_SHORT).show() } else if(binding.Phonenumber.text.trim().length != 10 ) { Toast.makeText(requireContext(), "Invalid Phone number", Toast.LENGTH_SHORT).show() } else { otpbhej(binding.editTextPhone.text.toString(),binding.Phonenumber.text.toString()) builder.show() } } private fun otpbhej(Cc: String, phoneNumber: String) { val options = PhoneAuthOptions.newBuilder(FirebaseAuth.getInstance()) .setPhoneNumber("+" + Cc + phoneNumber) // Phone number to verify .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit .setActivity(requireActivity()) // Activity (for callback binding) .setCallbacks(callbacks) // OnVerificationStateChangedCallbacks .build() PhoneAuthProvider.verifyPhoneNumber(options) } val callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(credential: PhoneAuthCredential) { } override fun onVerificationFailed(e: FirebaseException) { } override fun onCodeSent( verificationId: String, token: PhoneAuthProvider.ForceResendingToken, ) { binding.Phonenumber.visibility=View.GONE binding.editTextPhone.visibility=View.GONE builder.dismiss() binding.OtpNumber.visibility=View.VISIBLE if(binding.OtpNumber.text.isNotEmpty()) { checkOTP(verificationId,binding.OtpNumber.text.toString()) } } } private fun checkOTP(verificationId: String, code: String) { val credential = PhoneAuthProvider.getCredential(verificationId!!, code) signInWithPhoneAuthCredential(credential) } private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { FirebaseAuth.getInstance().signInWithCredential(credential) .addOnCompleteListener(requireActivity()) { task -> if (task.isSuccessful) { Toast.makeText(requireContext(), "You are signed in", Toast.LENGTH_SHORT).show() val user = task.result?.user } else { Toast.makeText(requireContext(), "Something went wrong", Toast.LENGTH_SHORT).show() } } } }
完整报错日志
Process: com.example.ecommerce, PID: 4130 java.lang.IllegalStateException: Fragment MoreFragment{f2ca588} (0a0f4eb7-5df8-4d00-aa6d-18838fd9f570) not attached to a context. at androidx.fragment.app.Fragment.requireContext(Fragment.java:967) at com.example.ecommerce.fragments.MoreFragment.<init>(MoreFragment.kt:22) at com.example.ecommerce.MainActivity$onCreate$2.invoke(MainActivity.kt:37) at com.example.ecommerce.MainActivity$onCreate$2.invoke(MainActivity.kt:31) at me.ibrahimsn.lib.SmoothBottomBar$setOnItemSelectedListener$1.onItemSelect(SmoothBottomBar.kt:613) at me.ibrahimsn.lib.SmoothBottomBar.onClickAction(SmoothBottomBar.kt:541) at me.ibrahimsn.lib.SmoothBottomBar.onTouchEvent(SmoothBottomBar.kt:523) at android.view.View.dispatchTouchEvent(View.java:14632) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2845) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2845) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2845) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2845) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2845) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2845) at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:573) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1911) at android.app.Activity.dispatchTouchEvent(Activity.java:4256) at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:70) at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:521) at android.view.View.dispatchPointerEvent(View.java:14899) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6755) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6526) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5995) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6057) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6018) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6192) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6026) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6249) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5999) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6057) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6018) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6026) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5999) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:9034) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8985) at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:8941) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:9177) at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:266) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:339) at android.os.Looper.loopOnce(Looper.java:179) at android.os.Looper.loop(Looper.java:344) at android.app.ActivityThread.main(ActivityThread.java:8212) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) E/AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034) I/Process: Sending signal. PID: 4130 SIG: 9
错误原因分析
从日志可直接定位核心问题:
- 第22行代码在Fragment构造阶段就调用
requireContext()初始化AlertDialog.Builder,此时Fragment还未附加到任何Activity/上下文,直接触发非法状态异常。 - 额外存在几处潜在风险:
onCreateView中错误返回null视图、未检查Fragment状态就调用requireContext()/requireActivity()。
修复方案
1. 延迟初始化AlertDialog
将builder的初始化移到onAttach方法中(此时上下文已可用):
private lateinit var builder: AlertDialog // 仅声明变量 override fun onAttach(context: Context) { super.onAttach(context) activityContext = context // 此时上下文已绑定,初始化弹窗 builder = AlertDialog.Builder(context) .setTitle("加载中.....") .setMessage("请稍候") .setCancelable(false) .create() }
2. 修复onCreateView的返回值错误
原代码中用户已登录时返回未初始化的view(实际为null),导致视图加载失败,改为返回binding.root:
@SuppressLint("SuspiciousIndentation") override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // 正确传入inflater和container参数 binding = FragmentMoreBinding.inflate(inflater, container, false) if(FirebaseAuth.getInstance().currentUser != null) { binding.login.visibility = View.GONE binding.textView5.visibility = View.GONE return binding.root // 返回正确的视图根节点 } // 其余点击事件逻辑保持不变... return binding.root }
3. 安全使用上下文
所有需要上下文的地方,先判断上下文是否可用:
- 修改
auth()方法:
private fun auth() { val context = activityContext ?: return if(binding.Phonenumber.text.isEmpty() || binding.editTextPhone.text.isEmpty()) { Toast.makeText(context, "请填写所有内容", Toast.LENGTH_SHORT).show() } else if (binding.editTextPhone.text.toString().trim().length != 2 ) { Toast.makeText(context, "无效的国家代码", Toast.LENGTH_SHORT).show() } else if(binding.Phonenumber.text.trim().length != 10 ) { Toast.makeText(context, "无效的手机号", Toast.LENGTH_SHORT).show() } else { otpbhej(binding.editTextPhone.text.toString(), binding.Phonenumber.text.toString()) builder.show() } }
- 修改
signInWithPhoneAuthCredential方法:
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { val currentActivity = activity ?: return FirebaseAuth.getInstance().signInWithCredential(credential) .addOnCompleteListener(currentActivity) { task -> val context = activityContext ?: return@addOnCompleteListener if (task.isSuccessful) { Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show() val user = task.result?.user } else { Toast.makeText(context, "操作失败,请重试", Toast.LENGTH_SHORT).show() } } }
4. 回调中检查Fragment状态
在Firebase的验证码回调里,先确认Fragment仍处于活跃状态,再操作UI:
val callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { // ...保留其他回调方法 override fun onCodeSent( verificationId: String, token: PhoneAuthProvider.ForceResendingToken, ) { if (!isAdded) return // 检查Fragment是否仍附加到Activity binding.Phonenumber.visibility = View.GONE binding.editTextPhone.visibility = View.GONE builder.dismiss() binding.OtpNumber.visibility = View.VISIBLE if(binding.OtpNumber.text.isNotEmpty()) { checkOTP(verificationId, binding.OtpNumber.text.toString()) } } }
内容的提问来源于stack exchange,提问作者k1rit0
相关产品推荐
相关产品推荐

