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

使用Firebase获取数据层数据时触发Picasso路径为空异常求助

Firebase Firestore异步数据加载导致Picasso空路径异常排查与修复

异常日志

E  FATAL EXCEPTION: main
Process: com.example.mvvmlesson, PID: 12328
java.lang.IllegalArgumentException: Path must not be empty.
at com.squareup.picasso.Picasso.load(Picasso.java:332)
at com.example.mvvmlesson.ui.GameFragment$observeViewModel$1.invoke(GameFragment.kt:54)
at com.example.mvvmlesson.ui.GameFragment$observeViewModel$1.invoke(GameFragment.kt:53)
at com.example.mvvmlesson.ui.GameFragment$sam$androidx_lifecycle_Observer$0.onChanged(Unknown Source:2)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:146)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:483)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:440)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.kt:314)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.kt:251)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.kt:287)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.kt:136)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.kt:119)
at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:100)
at androidx.fragment.app.Fragment.performStart(Fragment.java:3194)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:628)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:290)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1943)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1845)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1782)
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:565)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8176)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

相关代码

GameFragment.kt

class GameFragment : Fragment() {
    
    private val viewModel: GameViewModel by lazy {
        ViewModelProvider(
            this,
            ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application)
        )[GameViewModel::class.java]
    }

    private var _binding: FragmentGameBinding? = null
    private val binding: FragmentGameBinding
        get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentGameBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        observeViewModel()
    }

    private fun observeViewModel() {
        viewModel.question.observe(viewLifecycleOwner) {
            Picasso.get().load(it.image.toString()).into(binding.imageViewQuestion)
        }
    }
}

GameViewModel.kt

class GameViewModel: ViewModel() {

    private val repository = GameRepositoryImpl
    
    private val generateQuestionUseCase = GenerateQuestionUseCase(repository)

    private val _question = MutableLiveData<Question>()
    val question: LiveData<Question>
// get() = _question

    fun startGame() {
        generateQuestion()
    }

     private fun generateQuestion() {
        _question.value = generateQuestionUseCase()
    }
}

GameRepositoryImpl.kt

object GameRepositoryImpl: GameRepository {

    override fun generateQuestion(): Question {

        val questionGame = Question("", "")

        val db = Firebase.firestore
        val random = (1..5).random()
        val question = db.collection("MOVIES").document(random.toString())
        question.get().addOnSuccessListener { document ->
            val quest = document.toObject<Question>()
            if (quest != null) {
                questionGame.answer = quest.answer.toString()
                questionGame.image = quest.image.toString()
            }
        }
        return questionGame
    }
}

GenerateQuestionUseCase.kt

class GenerateQuestionUseCase (private val repository: GameRepository) {
    operator fun invoke(): Question {
        return repository.generateQuestion()
    }
}

GameRepository.kt

interface GameRepository {
    fun generateQuestion(): Question
}

问题根源

  1. 异步请求同步返回错误:Firestore的get()是异步操作,GameRepositoryImpl.generateQuestion会立即返回初始值为Question("", "")的对象,此时Firestore请求还未完成,image字段为空字符串。
  2. LiveData提前推送空数据:ViewModel直接将这个空数据赋值给_question.value,Fragment观察到后调用Picasso.load("")触发空路径异常。
  3. ViewModel代码缺陷:question的getter被注释掉,导致外部无法正确获取LiveData实例,加剧了问题。

修复方案

1. 修复ViewModel的LiveData getter

恢复GameViewModel中question的getter,确保外部能正确观察LiveData:

val question: LiveData<Question>
    get() = _question

2. 改造Repository为异步返回数据

Firestore操作是异步的,不能同步返回数据,改用LiveData传递结果:

更新GameRepository接口

interface GameRepository {
    fun generateQuestion(): LiveData<Question>
}

更新GameRepositoryImpl实现

object GameRepositoryImpl: GameRepository {
    override fun generateQuestion(): LiveData<Question> {
        val resultLiveData = MutableLiveData<Question>()
        val db = Firebase.firestore
        val randomId = (1..5).random().toString()
        
        db.collection("MOVIES").document(randomId)
            .get()
            .addOnSuccessListener { doc ->
                // 优先使用Firestore返回的数据,空则用默认值
                resultLiveData.value = doc.toObject<Question>() ?: Question("", "")
            }
            .addOnFailureListener {
                // 请求失败时返回默认值,也可以自定义错误状态
                resultLiveData.value = Question("", "")
            }
        return resultLiveData
    }
}

更新GenerateQuestionUseCase

class GenerateQuestionUseCase(private val repository: GameRepository) {
    operator fun invoke(): LiveData<Question> {
        return repository.generateQuestion()
    }
}

更新GameViewModel

class GameViewModel: ViewModel() {
    private val repository = GameRepositoryImpl
    private val generateQuestionUseCase = GenerateQuestionUseCase(repository)

    private val _question = MutableLiveData<Question>()
    val question: LiveData<Question>
        get() = _question

    fun startGame() {
        generateQuestion()
    }

    private fun generateQuestion() {
        // 使用viewModelScope管理生命周期,避免内存泄漏
        viewModelScope.launch {
            generateQuestionUseCase().asFlow().collect {
                _question.value = it
            }
        }
    }
}

3. Fragment添加空值校验

加载图片前检查image字段是否为空,避免Picasso抛出异常:

private fun observeViewModel() {
    viewModel.question.observe(viewLifecycleOwner) { question ->
        if (question.image.isNotBlank()) {
            Picasso.get().load(question.image).into(binding.imageViewQuestion)
        } else {
            // 设置默认占位图,提示用户图片加载失败或不存在
            binding.imageViewQuestion.setImageResource(R.drawable.default_question_image)
        }
    }
}

额外优化

  • 推荐使用Kotlin Flow替代LiveData,更贴合Kotlin异步编程生态。
  • 新增加载状态LiveData,在Fragment中显示加载动画,提升用户体验。
  • 处理Firestore请求失败的场景,比如Toast提示用户或重试机制。

内容的提问来源于stack exchange,提问作者Andrei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:09:52