Home > Software engineering >  ViewModel does not save recyclerview data when on configuration changed (device rotate)
ViewModel does not save recyclerview data when on configuration changed (device rotate)

Time:09-13

I just noticed problem earlier in my app, I see the ViewModel inside fragment doesn't save/keep recycler view when I rotate the device, I don't want to use the old method like save data in bundle onSaveInstanceState and restore it, I tried to figure why this problem by printing some logs on each method in fragment lifecycle but I didn't succeed

GIF showing the problem

the ViewModel

@HiltViewModel
class PostViewModel @Inject constructor(
    private val mainRepository: MainRepository,
    private val dataStoreRepository: DataStoreRepository,
    application: Application
) :
    AndroidViewModel(application) {


    /** ROOM DATABASE */
    val readAllPosts: LiveData<List<Item>> = mainRepository.localDataSource.getAllItems().asLiveData()
    val postsBySearchInDB: MutableLiveData<List<Item>> = MutableLiveData()

    /** RETROFIT **/
    var postsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
    var searchedPostsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()

    var postListResponse: PostList? = null

    var postListByLabelResponse: PostList? = null

    var searchPostListResponse: PostList? = null

    val label = MutableLiveData<String>()
    var finalURL: MutableLiveData<String?> = MutableLiveData()
    val token = MutableLiveData<String?>()

    val currentDestination = MutableLiveData<Int>()


    fun getCurrentDestination() {
        viewModelScope.launch {
            dataStoreRepository.readCurrentDestination.collect {
                currentDestination.value = it
            }

        }
    }

    val errorCode = MutableLiveData<Int>()
    val searchError = MutableLiveData<Boolean>()
    var networkStats = false
    var backOnline = false

    val recyclerViewLayout = dataStoreRepository.readRecyclerViewLayout.asLiveData()
    val readBackOnline = dataStoreRepository.readBackOnline.asLiveData()

    override fun onCleared() {
        super.onCleared()
        finalURL.value = null
        token.value = null
    }


    private fun saveBackOnline(backOnline: Boolean) = viewModelScope.launch {
        dataStoreRepository.saveBackOnline(backOnline)
    }

    fun saveCurrentDestination(currentDestination: Int) {
        viewModelScope.launch {
            dataStoreRepository.saveCurrentDestination(currentDestination)
        }
    }

    fun saveRecyclerViewLayout(layout: String) {
        viewModelScope.launch {
            dataStoreRepository.saveRecyclerViewLayout(layout)
        }
    }


    fun getPosts() = viewModelScope.launch {
        getPostsSafeCall()
    }

    fun getPostListByLabel() = viewModelScope.launch {
        getPostsByLabelSafeCall()
    }

    fun getItemsBySearch() = viewModelScope.launch {
        getItemsBySearchSafeCall()
    }

    private suspend fun getPostsByLabelSafeCall() {
        postsResponse.value = NetworkResult.Loading()

        if (hasInternetConnection()) {
            try {

                val response = mainRepository.remoteDataSource.getPostListByLabel(finalURL.value!!)
                postsResponse.value = handlePostsByLabelResponse(response)

            } catch (ex: HttpException) {
                Log.e(TAG, ex.message   ex.cause)
                postsResponse.value = NetworkResult.Error(ex.message.toString())
                errorCode.value = ex.code()

            } catch (ex: NullPointerException) {
                postsResponse.value = NetworkResult.Error("There's no items")
            }
        } else {
            postsResponse.value = NetworkResult.Error("No Internet Connection.")
        }
    }

    private suspend fun getPostsSafeCall() {
        postsResponse.value = NetworkResult.Loading()
        if (hasInternetConnection()) {
            try {

                if (finalURL.value.isNullOrEmpty()) {
                    finalURL.value = "$BASE_URL?key=$API_KEY"
                }

                val response = mainRepository.remoteDataSource.getPostList(finalURL.value!!)
                postsResponse.value = handlePostsResponse(response)

            } catch (e: Exception) {
                postsResponse.value = NetworkResult.Error(e.message.toString())

                if (e is HttpException) {
                    errorCode.value = e.code()
                    Log.e(TAG, "getPostsSafeCall: errorCode $errorCode")
                    Log.e(TAG, "getPostsSafeCall: ${e.message.toString()}")
                }
            }
        } else {
            postsResponse.value = NetworkResult.Error("No Internet Connection.")
        }
    }


    private fun handlePostsResponse(response: Response<PostList>): NetworkResult<PostList> {
        if (response.isSuccessful) {

            if (!(token.value.equals(response.body()?.nextPageToken))) {
                token.value = response.body()?.nextPageToken

                response.body()?.let { resultResponse ->
                    Log.d(
                        TAG, "handlePostsResponse: old token is: ${token.value} "  
                                "new token is: ${resultResponse.nextPageToken}"
                    )


                    finalURL.value = "${BASE_URL}?pageToken=${token.value}&key=${API_KEY}"

                    Log.e(TAG, "handlePostsResponse finalURL is ${finalURL.value!!}")

                    for (item in resultResponse.items) {
                        insertItem(item)
                    }
                    return NetworkResult.Success(resultResponse)
                    

                }
            }



        }
        if (token.value == null) {
            errorCode.value = 400
        } else {
            errorCode.value = response.code()
        }

        return NetworkResult.Error(
            "network results of handlePostsResponse ${
                response.body().toString()
            }"
        )
    }

    private fun handlePostsByLabelResponse(response: Response<PostList>): NetworkResult<PostList> {

        if (response.isSuccessful) {
            response.body()?.let { resultResponse ->
                Log.d(
                    TAG, "handlePostsByLabelResponse: old token is: ${token.value} "  
                            "new token is: ${resultResponse.nextPageToken}"
                )

                finalURL.postValue(
                    (BASE_URL_POSTS_BY_LABEL   "posts?labels=${label.value}"
                              "&maxResults=20"
                              "&pageToken=")
                              token.value
                              "&key="   API_KEY
                )

                if (postListByLabelResponse == null) {
                    postListByLabelResponse = resultResponse
                } else {
                    val oldPosts = postListByLabelResponse?.items
                    val newPosts = resultResponse.items
                    oldPosts?.addAll(newPosts)
                }



                return NetworkResult.Success(postListByLabelResponse?:resultResponse)


            }
        }
        if (token.value == null) {
            errorCode.value = 400
        } else {
            errorCode.value = response.code()
        }
        Log.e(TAG, "handlePostsByLabelResponse: final URL ${finalURL.value}")
  

        return NetworkResult.Error(
            "network results of handlePostsByLabelResponse ${
                response.body().toString()
            }"
        )
    }


    private fun hasInternetConnection(): Boolean {

        val connectivityManager = getApplication<Application>().getSystemService(
            Context.CONNECTIVITY_SERVICE
        ) as ConnectivityManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val activeNetwork = connectivityManager.activeNetwork ?: return false
            val capabilities =
                connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
            return when {
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> true
                else -> false
            }
        } else {
            val networkInfo = connectivityManager.activeNetworkInfo
            return networkInfo != null && networkInfo.isConnectedOrConnecting
        }

    }

    fun showNetworkStats() {
        if (!networkStats) {
            Toast.makeText(getApplication(), "No Internet connection", Toast.LENGTH_SHORT).show()
            saveBackOnline(true)
        } else if (networkStats) {
            if (backOnline) {
                Toast.makeText(getApplication(), "We're back online", Toast.LENGTH_SHORT).show()
                saveBackOnline(false)
            }
        }
    }

    private fun insertItem(item: Item) {
        viewModelScope.launch(Dispatchers.IO) {
            mainRepository.localDataSource.insertItem(item)
        }
    }



    private suspend fun getItemsBySearchSafeCall() {
        searchedPostsResponse.value = NetworkResult.Loading()
        if (!label.value.isNullOrEmpty()) {
            finalURL.value = "${BASE_URL}?labels=${label.value}&maxResults=500&key=$API_KEY"
        }

        Log.e(TAG, "getItemsBySearch: ${finalURL.value}")

        if (hasInternetConnection()) {
            try {
                val response = mainRepository.remoteDataSource.getPostListBySearch(finalURL.value!!)

                searchedPostsResponse.value = handlePostsBySearchResponse(response)

            } catch (e: Exception) {
                searchedPostsResponse.value = NetworkResult.Error(e.message.toString())
            }
        } else {
            searchedPostsResponse.value = NetworkResult.Error("No Internet Connection.")
        }

    }

    private fun handlePostsBySearchResponse(response: Response<PostList>): NetworkResult<PostList> {
        return if (response.isSuccessful) {
            val postListResponse = response.body()
            NetworkResult.Success(postListResponse!!)
        } else {
            errorCode.value = response.code()
            NetworkResult.Error(response.errorBody().toString())

        }
    }


    fun getItemsBySearchInDB(keyword: String) {
        Log.d(TAG, "getItemsBySearchInDB: called")
        viewModelScope.launch {
            val items = mainRepository.localDataSource.getItemsBySearch(keyword)
            if (items.isNotEmpty()) {
                postsBySearchInDB.value = items
            } else {
                searchError.value = true
                Log.e(TAG, "list is empty")
            }

        }
    }

}

the fragment

@AndroidEntryPoint
class AccessoryFragment : Fragment(), MenuProvider, TitleAndGridLayout {

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


    private var searchItemList = arrayListOf<Item>()
    private lateinit var postViewModel: PostViewModel


    private val titleLayoutManager: GridLayoutManager by lazy { GridLayoutManager(context, 2) }
    private val gridLayoutManager: GridLayoutManager by lazy { GridLayoutManager(context, 3) }
    private var linearLayoutManager: LinearLayoutManager? = null

    private val KEY_RECYCLER_STATE = "recycler_state"
    private val mBundleRecyclerViewState by lazy { Bundle() }

    private lateinit var adapter: PostAdapter

    private var isScrolling = false
    var currentItems = 0
    var totalItems: Int = 0
    var scrollOutItems: Int = 0
    private var postsAPiFlag = false

    private var keyword: String? = null
    private lateinit var networkListener: NetworkListener
    
    private var networkStats = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        postViewModel = ViewModelProvider(this)[PostViewModel::class.java]
        adapter = PostAdapter(this)

        postViewModel.finalURL.value =
            BASE_URL_POSTS_BY_LABEL   "posts?labels=Accessory&maxResults=20&key=$API_KEY"

        networkListener = NetworkListener()

    }


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

        val menuHost: MenuHost = requireActivity()
        menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.CREATED)

        postViewModel.label.value = "Accessory"

        return binding.root
    }

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

        Log.d(TAG, "onViewCreated: called")

        postViewModel.recyclerViewLayout.observe(viewLifecycleOwner) { layout ->

            linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
            Log.w(TAG, "onViewCreated getSavedLayout called")
            when (layout) {
                "cardLayout" -> {
                    binding.accessoryRecyclerView.layoutManager = linearLayoutManager
                    adapter.viewType = 0
                    binding.accessoryRecyclerView.adapter = adapter
                }
                "cardMagazineLayout" -> {
                    binding.accessoryRecyclerView.layoutManager = linearLayoutManager
                    adapter.viewType = 1
                    binding.accessoryRecyclerView.adapter = adapter
                }
                "titleLayout" -> {
                    binding.accessoryRecyclerView.layoutManager = titleLayoutManager
                    adapter.viewType = 2
                    binding.accessoryRecyclerView.adapter = adapter
                }
                "gridLayout" -> {
                    binding.accessoryRecyclerView.layoutManager = gridLayoutManager
                    adapter.viewType = 3
                    binding.accessoryRecyclerView.adapter = adapter
                }
            }
        }

        lifecycleScope.launchWhenStarted {
            networkListener.checkNetworkAvailability(requireContext()).collect { stats ->
                Log.d(TAG, "networkListener: $stats")
                postViewModel.networkStats = stats
                postViewModel.showNetworkStats()
                [email protected] = stats
                if (stats ) {

                    if (binding.accessoryRecyclerView.visibility == View.GONE) {
                        binding.accessoryRecyclerView.visibility = View.VISIBLE
                    }
                    requestApiData()


                } else {

//                    Log.d(TAG, "onViewCreated: savedInstanceState $savedInstanceState")
                    noInternetConnectionLayout()
                }
            }
        }

        binding.accessoryRecyclerView.onItemClick { _, position, _ ->
            val postItem = adapter.differ.currentList[position]
            findNavController().navigate(
                AccessoryFragmentDirections.actionNavAccessoryToDetailsActivity(
                    postItem
                )
            )
        }



        binding.accessoryRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    isScrolling = true
                }

            }

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)



                currentItems = linearLayoutManager!!.childCount
                totalItems = adapter.itemCount
                scrollOutItems = linearLayoutManager!!.findFirstVisibleItemPosition()
                if ((!recyclerView.canScrollVertically(1) && dy > 0) &&
                    (isScrolling && currentItems   scrollOutItems >= totalItems && postsAPiFlag)
                ) {
                    hideShimmerEffect()
                    postViewModel.getPostListByLabel()
                    isScrolling = false
                }
            }
        })

        postViewModel.errorCode.observe(viewLifecycleOwner) { errorCode ->
            if (errorCode == 400) {
                binding.accessoryRecyclerView.setPadding(0, 0, 0, 0)
                Toast.makeText(requireContext(), R.string.lastPost, Toast.LENGTH_LONG).show()
                binding.progressBar.visibility = View.GONE
            } else {
                Log.e(TAG, "onViewCreated: ${postViewModel.errorCode.value.toString()} ")
                noInternetConnectionLayout()
            }
        }
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        Log.d(TAG, "onConfigurationChanged: ${newConfig.orientation}")

        Log.d(TAG, "onConfigurationChanged: ${adapter.differ.currentList.toString()}")
        Log.d(
            TAG,
            "onConfigurationChanged: "  
                    binding.accessoryRecyclerView.layoutManager?.itemCount.toString()
        )

    }

 
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        Log.d(TAG, "onViewStateRestored: called")
    }

    private fun requestApiData() {
        showShimmerEffect()
        Log.d(TAG, "requestApiData: called")
        postViewModel.getPostListByLabel()
        postViewModel.postsResponse.observe(viewLifecycleOwner) { response ->
            postsAPiFlag = true
            when (response) {
                is NetworkResult.Success -> {
                    hideShimmerEffect()
                    response.data?.let {
                        binding.progressBar.visibility = View.GONE

//                        itemArrayList.addAll(it.items)
                        adapter.differ.submitList(it.items.toList())

                    }

                }

                is NetworkResult.Error -> {
                    hideShimmerEffect()
                    binding.progressBar.visibility = View.GONE

                    Log.e(TAG, response.data.toString())
                    Log.e(TAG, response.message.toString())
                }

                is NetworkResult.Loading -> {
                    binding.progressBar.visibility = View.VISIBLE


                }
            }

        }
    }


    private fun showShimmerEffect() {
        binding.apply {
            shimmerLayout.visibility = View.VISIBLE
            accessoryRecyclerView.visibility = View.INVISIBLE
        }

    }

    private fun hideShimmerEffect() {
        binding.apply {
            shimmerLayout.stopShimmer()
            shimmerLayout.visibility = View.GONE
            accessoryRecyclerView.visibility = View.VISIBLE
        }
    }

    private fun changeAndSaveLayout() {
        Log.w(TAG, "changeAndSaveLayout: called")
        val builder = AlertDialog.Builder(requireContext())
        builder.setTitle(getString(R.string.choose_layout))
        val recyclerViewLayouts = resources.getStringArray(R.array.RecyclerViewLayouts)
        //        SharedPreferences.Editor editor = sharedPreferences.edit();
        builder.setItems(
            recyclerViewLayouts
        ) { _: DialogInterface?, index: Int ->
            try {
                when (index) {
                    0 -> {
                        adapter.viewType = 0
                        binding.accessoryRecyclerView.layoutManager = linearLayoutManager
                        binding.accessoryRecyclerView.adapter = adapter
                        postViewModel.saveRecyclerViewLayout("cardLayout")
                    }
                    1 -> {
                        adapter.viewType = 1
                        binding.accessoryRecyclerView.layoutManager = linearLayoutManager
                        binding.accessoryRecyclerView.adapter = adapter
                        postViewModel.saveRecyclerViewLayout("cardMagazineLayout")

                    }
                    2 -> {
                        adapter.viewType = 2
                        binding.accessoryRecyclerView.layoutManager = titleLayoutManager
                        binding.accessoryRecyclerView.adapter = adapter
                        postViewModel.saveRecyclerViewLayout("titleLayout")
                    }
                    3 -> {
                        adapter.viewType = 3
                        binding.accessoryRecyclerView.layoutManager = gridLayoutManager
                        binding.accessoryRecyclerView.adapter = adapter

                        postViewModel.saveRecyclerViewLayout("gridLayout")
                    }
                }
            } catch (e: Exception) {
                Log.e(TAG, "changeAndSaveLayout: "   e.message)
                Log.e(TAG, "changeAndSaveLayout: "   e.cause)
            }
        }
        val alertDialog = builder.create()
        alertDialog.show()
    }


    private fun noInternetConnectionLayout() {
        binding.apply {
//            accessoryRecyclerView.removeAllViews()
            Log.d(TAG, "noInternetConnectionLayout: called")
            shimmerLayout.stopShimmer()
            shimmerLayout.visibility = View.GONE
            accessoryRecyclerView.visibility = View.GONE
        }
        binding.noInternetConnectionLayout.inflate()

        binding.noInternetConnectionLayout.let {
            if (networkStats) {
                it.visibility = View.GONE
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
//        adapter.isDestroyed = true
        linearLayoutManager?.removeAllViews()
//        adView.destroy()
        linearLayoutManager = null
        _binding = null
    }

    override fun onDetach() {
        super.onDetach()
        if(linearLayoutManager != null){
            linearLayoutManager = null
        }
    }

    override fun tellFragmentToGetItems() {
        if (postViewModel.recyclerViewLayout.value.equals("titleLayout")
            || postViewModel.recyclerViewLayout.value.equals("gridLayout")
        ) {
            hideShimmerEffect()
            postViewModel.getPostListByLabel()
        }
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menuInflater.inflate(R.menu.main, menu)
        val searchManager =
            requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
        val searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
        searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
        searchView.queryHint = resources.getString(R.string.searchForPosts)



        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(keyword: String): Boolean {
                if (keyword.isEmpty()) {
                    Snackbar.make(
                        requireView(),
                        "please enter keyword to search",
                        Snackbar.LENGTH_SHORT
                    ).show()
                }
//                itemArrayList.clear()
                [email protected] = keyword
                requestSearchApi(keyword)

                return false

            }


            override fun onQueryTextChange(newText: String): Boolean {
                return false
            }
        })
        searchView.setOnCloseListener {
            if (keyword.isNullOrEmpty()) {
                hideShimmerEffect()
                return@setOnCloseListener false
            }
            if (Utils.hasInternetConnection(requireContext())) {
                showShimmerEffect()

                postViewModel.postListByLabelResponse = null

                searchItemList.clear()
//                adapter.differ.submitList(ArrayList())

                linearLayoutManager?.removeAllViews()
                binding.accessoryRecyclerView.removeAllViews()

//                itemArrayList.clear()
                adapter.differ.submitList(null)

                postViewModel.finalURL.value =
                    BASE_URL_POSTS_BY_LABEL   "posts?labels=Accessory&maxResults=20&key=$API_KEY"

                requestApiData()


//                itemArrayList.clear()
//                adapter.submitList(itemArrayList)


                //====> Here I call the request api method again


                Log.d(
                    TAG,
                    "setOnCloseListener: called ${adapter.differ.currentList.size.toString()}"
                )
//                adapter.notifyDataSetChanged()
//                binding.progressBar.visibility = View.GONE
//


                Log.d(TAG, "setOnCloseListener: ${postViewModel.finalURL.value.toString()}")
//
//                    adapter.notifyDataSetChanged()
//                }
            } else {
                Log.d(TAG, "setOnCloseListener: called")
                adapter.differ.submitList(null)
                searchItemList.clear()
                noInternetConnectionLayout()
            }
            false
        }


        postViewModel.searchError.observe(viewLifecycleOwner) { searchError ->
            if (searchError) {
                Toast.makeText(
                    requireContext(),
                    "There's no posts with this keyword", Toast.LENGTH_LONG
                ).show()
            }
        }
    }

    private fun requestSearchApi(keyword: String) {

        if (Utils.hasInternetConnection(requireContext())) {
            showShimmerEffect()

            postViewModel.finalURL.value =
                "${BASE_URL}?labels=Accessory&maxResults=500&key=$API_KEY"

            postViewModel.getItemsBySearch()
            postViewModel.searchedPostsResponse.observe(viewLifecycleOwner) { response ->
                when (response) {
                    is NetworkResult.Success -> {
                        postsAPiFlag = false
                        //                                adapter.differ.currentList.clear()

                        if (searchItemList.isNotEmpty()) {
                            searchItemList.clear()
                        }

                        binding.progressBar.visibility = View.GONE

                        lifecycleScope.launch {
                            withContext(Dispatchers.Default) {
                                searchItemList.addAll(response.data?.items?.filter {
                                    it.title.contains(keyword) || it.content.contains(keyword)
                                } as ArrayList<Item>)
                            }
                        }

                        Log.d(TAG, "requestSearchApi: test size ${searchItemList.size}")


                        if (searchItemList.isEmpty()) {
//                                adapter.differ.submitList(null)
                            Toast.makeText(
                                requireContext(),
                                "The search word was not found in any post",
                                Toast.LENGTH_SHORT
                            ).show()
                            hideShimmerEffect()
                            return@observe

                        } else {
                            postsAPiFlag = false
//                            itemArrayList.clear()
                            adapter.differ.submitList(null)

                            hideShimmerEffect()
//                            Log.d(
////                                TAG, "requestSearchApi: searchItemList ${searchItemList[0].title}"
//                            )
                            adapter.differ.submitList(searchItemList)
//                            binding.accessoryRecyclerView.scrollToPosition(0)
                        }

                    }
                    is NetworkResult.Error -> {
                        hideShimmerEffect()
                        binding.progressBar.visibility = View.GONE
                        Toast.makeText(
                            requireContext(),
                            response.message.toString(),
                            Toast.LENGTH_SHORT
                        ).show()
                        Log.e(TAG, "onQueryTextSubmit: $response")

                    }

                    is NetworkResult.Loading -> {
                        binding.progressBar.visibility = View.VISIBLE

                    }
                }
            }
        } else {
            noInternetConnectionLayout()
        }
    }

    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        return if (menuItem.itemId == R.id.change_layout) {
            changeAndSaveLayout()
            true
        } else false
    }


}

CodePudding user response:

Unless I'm missing something (that's a lot of code to go through!) you don't set any data on your adapter until this bit:

private fun requestApiData() {
    postViewModel.getPostListByLabel()
    postViewModel.postsResponse.observe(viewLifecycleOwner) {
        ...
        adapter.differ.submitList(it.items.toList())
}

And getPostListByLabel() clears the current data in postsResponse

fun getPostListByLabel() = viewModelScope.launch {
    getPostsByLabelSafeCall()
}

private suspend fun getPostsByLabelSafeCall() {
    postsResponse.value = NetworkResult.Loading()
    // fetch data over network and update postsResponse with it later
    ...
}

So when you first observe it, it's in the NetworkResult.Loading state - any posts you had stored have been wiped.

Your Fragment gets recreated when the Activity is rotated and destroyed - so if you're initialising the ViewModel data contents as part of that Fragment setup (like you're doing here) it's going to get reinitialised every time the Fragment is recreated, and you'll lose the current data.

You'll need to work out a way to avoid that happening - you don't actually want to do that clear-and-fetch whenever a Fragment is created, so you'll have to decide when it should happen. Maybe when the ViewModel is first created (i.e. through the init block), maybe the first time a Fragment calls it (e.g. create an initialised boolean in the VM set to false, check it in the call, set true when it runs). Or maybe just when postsResponse has no value yet (postsResponse.value == null). I don't know the flow of your application so you'll have to work out when to force a fetch and when to keep the data that's already there

  • Related