You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

集成手机号验证后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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 13:49:54