使用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 }
问题根源
- 异步请求同步返回错误:Firestore的
get()是异步操作,GameRepositoryImpl.generateQuestion会立即返回初始值为Question("", "")的对象,此时Firestore请求还未完成,image字段为空字符串。 - LiveData提前推送空数据:ViewModel直接将这个空数据赋值给
_question.value,Fragment观察到后调用Picasso.load("")触发空路径异常。 - 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
相关产品推荐
相关产品推荐

