如何在MainActivity加载前通过ViewModel加载Open Weather API数据?
问题解决思路
核心问题定位
你的崩溃主要来自两个关键问题:
- ViewModel中
_openWeather.value!!的非空断言——若API请求失败或尚未返回时,_openWeather.value为null,直接触发空指针异常。 - UI层观察
openWeatherLiveData时未处理初始null状态,直接访问数据会导致崩溃。
下面提供两种可行的解决方案,你可以根据产品需求选择:
方案一:等待API加载完成后再填充UI
这种方式会先显示加载状态,请求成功后展示真实数据,失败则显示错误提示,适合对数据真实性要求高的场景。
修改ViewModel
去掉冗余的_name LiveData,添加加载、错误状态来通知UI,同时移除非空断言避免崩溃:
class WeatherViewModel : ViewModel() { private val _searchText = MutableStateFlow("") val searchText = _searchText.asStateFlow() private val _isSearching = MutableStateFlow(false) val isSearching = _isSearching.asStateFlow() private val _openWeather = MutableLiveData<OpenWeather>() val openWeather: LiveData<OpenWeather> = _openWeather // 新增加载/错误状态 private val _isLoading = MutableLiveData(false) val isLoading: LiveData<Boolean> = _isLoading private val _errorMessage = MutableLiveData<String?>() val errorMessage: LiveData<String?> = _errorMessage init { getWeatherData() } private fun getWeatherData() { viewModelScope.launch { _isLoading.value = true _errorMessage.value = null try { val weatherData = RetrofitInstance.api.getWeatherUSCity() _openWeather.value = weatherData Log.v("Name: ", "${weatherData.name}") Log.v("WeatherViewModel", "Success!") } catch (e: Exception) { Log.e("WeatherViewModel", "Failure: ${e.message}") _errorMessage.value = e.message ?: "加载失败" } finally { _isLoading.value = false } } } fun onSearchTextChanged(text: String) { _searchText.value = text } fun emptyText() { _searchText.value = "" } }
修改MainActivity UI逻辑
根据加载、错误、数据状态切换显示内容:
setContent { WeatherTheme { val text by viewModel.searchText.collectAsState() var active by remember { mutableStateOf(false) } val weather by viewModel.openWeather.observeAsState() val isLoading by viewModel.isLoading.observeAsState(false) val errorMessage by viewModel.errorMessage.observeAsState() val prevItems = remember { mutableListOf<String>() } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { // 保留原SearchBar代码不变 SearchBar( modifier = Modifier.fillMaxWidth(), query = text, onQueryChange = viewModel::onSearchTextChanged, onSearch = { if(prevItems.size > 14) prevItems.clear() prevItems.add(text) active = false }, active = active, onActiveChange = { active = it }, placeholder = { Text(text = "Search") }, leadingIcon = { Icon( imageVector = Icons.Default.Search, contentDescription = "Search Icon" ) }, trailingIcon = { if (active) { Icon( modifier = Modifier.clickable { if (text.isNotEmpty()) { viewModel.emptyText() } else { active = false } }, imageVector = Icons.Default.Close, contentDescription = "Close Icon" ) } } ) { prevItems.forEach { Row(modifier = Modifier .padding(all = 14.dp) .clickable { viewModel.onSearchTextChanged(it) } .fillMaxWidth()) { Icon( modifier = Modifier.padding(end = 10.dp), imageVector = Icons.Default.History, contentDescription = "Search History Icon" ) Text(it) } } } // 根据状态展示不同内容 when { isLoading -> CircularProgressIndicator() errorMessage != null -> Text(text = errorMessage!!, color = Color.Red) weather != null -> Text(text = weather!!.name) } } } }
方案二:用假数据占位,API完成后自动更新
利用你已经给OpenWeather设置的默认值,让UI先显示占位数据,API请求成功后自动替换为真实数据,适合追求流畅体验的场景。
修改ViewModel
初始化_openWeather时设置默认假数据,移除非空断言:
class WeatherViewModel : ViewModel() { // 保留其他代码不变 // 初始化时设置默认假数据 private val _openWeather = MutableLiveData<OpenWeather>(OpenWeather(name = "加载中...")) val openWeather: LiveData<OpenWeather> = _openWeather private fun getWeatherData() { viewModelScope.launch { Log.v("WeatherViewModel", "Loading...") try { val weatherData = RetrofitInstance.api.getWeatherUSCity() _openWeather.value = weatherData Log.v("Name: ", "${weatherData.name}") Log.v("WeatherViewModel", "Success!") } catch (e: Exception) { Log.e("WeatherViewModel", "Failure: ${e.message}") } } } // 保留其他代码不变 }
修改MainActivity UI逻辑
直接使用weather数据,因为初始有默认值,无需处理null:
setContent { WeatherTheme { val text by viewModel.searchText.collectAsState() var active by remember { mutableStateOf(false) } // 这里也可以设置默认值,和ViewModel保持一致 val weather by viewModel.openWeather.observeAsState(OpenWeather(name = "加载中...")) val prevItems = remember { mutableListOf<String>() } Column( modifier = Modifier.fillMaxSize() ) { // 保留原SearchBar代码不变 SearchBar( modifier = Modifier.fillMaxWidth(), query = text, onQueryChange = viewModel::onSearchTextChanged, onSearch = { if(prevItems.size > 14) prevItems.clear() prevItems.add(text) active = false }, active = active, onActiveChange = { active = it }, placeholder = { Text(text = "Search") }, leadingIcon = { Icon( imageVector = Icons.Default.Search, contentDescription = "Search Icon" ) }, trailingIcon = { if (active) { Icon( modifier = Modifier.clickable { if (text.isNotEmpty()) { viewModel.emptyText() } else { active = false } }, imageVector = Icons.Default.Close, contentDescription = "Close Icon" ) } } ) { prevItems.forEach { Row(modifier = Modifier .padding(all = 14.dp) .clickable { viewModel.onSearchTextChanged(it) } .fillMaxWidth()) { Icon( modifier = Modifier.padding(end = 10.dp), imageVector = Icons.Default.History, contentDescription = "Search History Icon" ) Text(it) } } } // 直接展示数据,API返回后自动更新 Text(text = weather.name) // 可扩展展示其他默认数据,比如weather.main.temp等 } } }
额外优化建议
- 彻底摒弃
!!非空断言,改用?.安全调用或?:Elvis运算符处理null场景。 - 可将初始化时的API请求改为用户点击搜索后触发,更符合常规交互逻辑。
- 给
OpenWeather的默认值设置更贴近真实场景的占位内容,比如name = "加载中...",提升用户感知。
内容的提问来源于stack exchange,提问作者Karan Vadhan
相关产品推荐
相关产品推荐

