首页 > 解决方案 > 无法根据用户的位置更新recycleview的内容

问题描述

在检索用户位置并根据在回收站视图中检索到的位置显示数据时,我遇到了一个奇怪的错误。对于上下文,每当应用程序从新启动(未授予权限)时,都可以检索该位置。但是,除非我关闭应用程序或按下底部导航栏,否则它不会在回收站视图中显示预期的内容。

示范:

https://i.imgur.com/9kc1Zxc.gif

分段

const val TAG = "ForecastFragment"

@AndroidEntryPoint
class ForecastFragment : Fragment(), SearchView.OnQueryTextListener,
    SwipeRefreshLayout.OnRefreshListener{

    private val viewModel: WeatherForecastViewModel by viewModels()

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

    private lateinit var forecastAdapter: ForecastAdapter
    private lateinit var searchMenuItem: MenuItem
    private lateinit var searchView: SearchView
    private lateinit var swipeRefreshLayout: SwipeRefreshLayout

    private var currentQuery: String? = null

    private lateinit var client: FusedLocationProviderClient

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.forecast_list_menu, menu)
        searchMenuItem = menu.findItem(R.id.menu_search)
        searchView = searchMenuItem.actionView as SearchView
        searchView.isSubmitButtonEnabled = true
        searchView.setOnQueryTextListener(this)
        return super.onCreateOptionsMenu(menu, inflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentForecastBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = this

        client = LocationServices.getFusedLocationProviderClient(requireActivity())
        swipeRefreshLayout = binding.refreshLayoutContainer

        setupRecyclerView()
        getLastLocation()
        updateSupportActionbarTitle()
        swipeRefreshLayout.setOnRefreshListener(this)

        return binding.root
    }

    private fun updateSupportActionbarTitle() {
        viewModel.queryMutable.observe(viewLifecycleOwner, Observer { query ->
            (activity as AppCompatActivity).supportActionBar?.title = query
        })
    }

    private fun setupRecyclerView() {
        forecastAdapter = ForecastAdapter()
        binding.forecastsRecyclerView.apply {
            adapter = forecastAdapter
            layoutManager = LinearLayoutManager(activity)
        }
    }

    private fun requestWeatherApiData() {
        lifecycleScope.launch {
            viewModel.weatherForecast.observe(viewLifecycleOwner, Observer { response ->
                when (response) {
                    is Resource.Success -> {
                        response.data?.let { forecastResponse ->
                            forecastAdapter.diff.submitList(forecastResponse.list)
                            swipeRefreshLayout.isRefreshing = false
                        }
                    }
                    is Resource.Error -> {
                        response.msg?.let { msg ->
                            Log.e(TAG, "An error occurred : $msg")
                            swipeRefreshLayout.isRefreshing = false
                        }
                    }
                    is Resource.Loading -> {
                        // nothing for now
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
            })
        }
    }

    private fun searchWeatherApiData(searchQuery: String) {
        viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
        viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
            when (response) {
                is Resource.Success -> {
                    response.data?.let { forecastResponse ->
                        forecastAdapter.diff.submitList(forecastResponse.list)
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Error -> {
                    response.msg?.let { msg ->
                        Log.e(TAG, "An error occurred : $msg")
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Loading -> {
                    swipeRefreshLayout.isRefreshing = false
                }
            }
        })
    }

    private fun getWeatherApiDataLocation(city: String) {
        viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
        viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
            when (response) {
                is Resource.Success -> {
                    response.data?.let { forecastResponse ->
                        forecastAdapter.diff.submitList(forecastResponse.list)
                        forecastAdapter.notifyDataSetChanged()
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Error -> {
                    response.msg?.let { msg ->
                        Log.e(TAG, "An error occurred : $msg")
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Loading -> {
                    swipeRefreshLayout.isRefreshing = false
                }
            }
        })
    }

    private fun fetchForecastAsync(query: String?) {
        if (query == null)
            requestWeatherApiData()
        else
            searchWeatherApiData(query)
    }

    override fun onQueryTextSubmit(query: String?): Boolean {
        if (query != null) {
            currentQuery = query
            viewModel.queryMutable.value = query
            searchWeatherApiData(query)
        }
        return true
    }

    override fun onQueryTextChange(newText: String?): Boolean {
        return true
    }

    override fun onRefresh() {
        currentQuery = viewModel.queryMutable.value
        fetchForecastAsync(currentQuery)
    }

    private fun isLocationEnabled(): Boolean {
        val locationManager: LocationManager =
            requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
                || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
    }

    private fun checkPermissions(): Boolean {
        if (ActivityCompat.checkSelfPermission(requireContext(),
                Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(requireContext(),
                Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
            return true
        }
        return false
    }

    private fun requestPermissions() {
        ActivityCompat.requestPermissions(
            requireActivity(),
            arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ID)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == PERMISSION_ID) {
            if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                getLastLocation()
            }
        }
    }

    @SuppressLint("MissingPermission")
    private fun getLastLocation() {
        if (checkPermissions()) {
            if (isLocationEnabled()) {

                client.lastLocation.addOnCompleteListener(requireActivity()) { task ->
                    val location: Location? = task.result
                    if (location == null) {
                        requestNewLocationData()
                    } else {
                        val geoCoder = Geocoder(requireContext(), Locale.getDefault())
                        val addresses = geoCoder.getFromLocation(
                            location.latitude, location.longitude, 1)

                        val city = addresses[0].locality
                        viewModel.queryMutable.value = city
                        getWeatherApiDataLocation(city)
                    }
                }
            } else {
                val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
                startActivity(intent)
            }
        } else {
            requestPermissions()
        }
    }

    @SuppressLint("MissingPermission")
    private fun requestNewLocationData() {
        val mLocationRequest = LocationRequest.create().apply {
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
            interval = 0
            fastestInterval = 0
            numUpdates = 1
        }
        client = LocationServices.getFusedLocationProviderClient(requireActivity())
        client.requestLocationUpdates(
            mLocationRequest, mLocationCallback,
            Looper.myLooper()
        )
    }

    private val mLocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            val mLastLocation: Location = locationResult.lastLocation
            mLastLocation.latitude.toString()
            mLastLocation.longitude.toString()
        }
    }
}

查看模型

@HiltViewModel
class WeatherForecastViewModel
@Inject constructor(
    private val repository: WeatherForecastRepository,
    application: Application) :
    AndroidViewModel(application) {

    private val unit  = "imperial"
    private var query = "Paris"

    val weatherForecast: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
    var searchWeatherForecastResponse: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
    val weatherForecastLocation: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
    var queryMutable: MutableLiveData<String> = MutableLiveData()

    init {
        //queryMutable.value = query
        //getWeatherForecast(queryMutable.value.toString(), unit)

    }

    private fun getWeatherForecast(query: String, units: String) =
        viewModelScope.launch {
            weatherForecast.postValue(Resource.Loading())
            val response = repository.getWeatherForecast(query, units)
            weatherForecast.postValue(weatherForecastResponseHandler(response))
        }

    private suspend fun searchWeatherForecastSafeCall(searchQuery: Map<String, String>) {
        searchWeatherForecastResponse.postValue(Resource.Loading())
        val response = repository.searchWeatherForecast(searchQuery)
        searchWeatherForecastResponse.postValue(weatherForecastResponseHandler(response))
    }

    private suspend fun getWeatherForecastLocationSafeCall(query: Map<String, String>) {
        weatherForecastLocation.postValue(Resource.Loading())
        val response = repository.getWeatherForecastLocation(query)
        weatherForecastLocation.postValue(weatherForecastResponseHandler(response))
    }

    fun searchWeatherForecast(searchQuery: Map<String, String>) =
        viewModelScope.launch {
        searchWeatherForecastSafeCall(searchQuery)
    }

    fun weatherForecastLocation(query: Map<String, String>) =
        viewModelScope.launch {
            getWeatherForecastLocationSafeCall(query)
        }

    fun applySearchQuery(searchQuery: String): HashMap<String, String> {
        val queries: HashMap<String, String> = HashMap()
        queries[QUERY_CITY] = searchQuery
        queries[QUERY_UNITS] = unit
        queries[QUERY_COUNT] = API_COUNT
        queries[QUERY_API] = API_KEY
        return queries
    }

    fun applyLocationQuery(query: String): HashMap<String, String> {
        val queries: HashMap<String, String> = HashMap()
        queries[QUERY_CITY] = query
        queries[QUERY_UNITS] = unit
        queries[QUERY_COUNT] = API_COUNT
        queries[QUERY_API] = API_KEY
        return queries
    }

    private fun weatherForecastResponseHandler(
        response: Response<WeatherForecastResponse>): Resource<WeatherForecastResponse> {
        if (response.isSuccessful) {
            response.body()?.let { result ->
                return Resource.Success(result)
            }
        }
        return Resource.Error(response.message())
    }
}

标签: androidandroid-fragmentskotlin-coroutinesandroid-viewmodel

解决方案


我觉得问题出在方式上,你正在设置你的观察者,你是在一个函数调用中设置它们,我认为这是错误的,那就是为同一件事设置多个观察者。它应该只设置一次。

所以我建议你把你的观察者带到一个单独的函数中,然后像这样调用它observeProperties()

所以你的代码会是这样的

private fun observeProperties() {
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
            when (response) {
                is Resource.Success -> {
                    response.data?.let { forecastResponse ->
                        forecastAdapter.diff.submitList(forecastResponse.list)
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Error -> {
                    response.msg?.let { msg ->
                        Log.e(TAG, "An error occurred : $msg")
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Loading -> {
                    swipeRefreshLayout.isRefreshing = false
                }
            }
        })
    viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
            when (response) {
                is Resource.Success -> {
                    response.data?.let { forecastResponse ->
                        forecastAdapter.diff.submitList(forecastResponse.list)
                        forecastAdapter.notifyDataSetChanged()
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Error -> {
                    response.msg?.let { msg ->
                        Log.e(TAG, "An error occurred : $msg")
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Loading -> {
                    swipeRefreshLayout.isRefreshing = false
                }
            }
        })
    viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
            when (response) {
                is Resource.Success -> {
                    response.data?.let { forecastResponse ->
                        forecastAdapter.diff.submitList(forecastResponse.list)
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Error -> {
                    response.msg?.let { msg ->
                        Log.e(TAG, "An error occurred : $msg")
                        swipeRefreshLayout.isRefreshing = false
                    }
                }
                is Resource.Loading -> {
                    swipeRefreshLayout.isRefreshing = false
                }
            }
        })
}

现在您可以在顶部调用此方法

        setupRecyclerView()
        getLastLocation()
        updateSupportActionbarTitle()
        swipeRefreshLayout.setOnRefreshListener(this)
        observeProperties()

您的其他方法现在将是这样的查询

private fun getWeatherApiDataLocation(city: String) {
        viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
 }
private fun searchWeatherApiData(searchQuery: String) {
        viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
}

推荐阅读