mograte MyListFragment to use a simple ViewModel
fixes crashes in MyListFragment if the User closes the fragment with a loading job still running, part of #56
This commit is contained in:
		| @ -1,17 +1,21 @@ | |||||||
| package org.mosad.teapod.ui.activity.main.fragments | package org.mosad.teapod.ui.activity.main.fragments | ||||||
|  |  | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
|  | import android.util.Log | ||||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
|  | import androidx.fragment.app.viewModels | ||||||
|  | import androidx.lifecycle.Lifecycle | ||||||
| import androidx.lifecycle.lifecycleScope | import androidx.lifecycle.lifecycleScope | ||||||
|  | import androidx.lifecycle.repeatOnLifecycle | ||||||
| import androidx.viewpager2.adapter.FragmentStateAdapter | import androidx.viewpager2.adapter.FragmentStateAdapter | ||||||
| import com.google.android.material.tabs.TabLayoutMediator | import com.google.android.material.tabs.TabLayoutMediator | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import org.mosad.teapod.R | import org.mosad.teapod.R | ||||||
| import org.mosad.teapod.databinding.FragmentMyListsBinding | import org.mosad.teapod.databinding.FragmentMyListsBinding | ||||||
| import org.mosad.teapod.parser.crunchyroll.Crunchyroll | import org.mosad.teapod.ui.activity.main.viewmodel.MyListsFragmentViewModel | ||||||
| import org.mosad.teapod.util.toItemMediaList | import org.mosad.teapod.util.toItemMediaList | ||||||
|  |  | ||||||
| class MyListsFragment : Fragment() { | class MyListsFragment : Fragment() { | ||||||
| @ -19,6 +23,8 @@ class MyListsFragment : Fragment() { | |||||||
|     private lateinit var binding: FragmentMyListsBinding |     private lateinit var binding: FragmentMyListsBinding | ||||||
|     private lateinit var pagerAdapter: FragmentStateAdapter |     private lateinit var pagerAdapter: FragmentStateAdapter | ||||||
|  |  | ||||||
|  |     private val model: MyListsFragmentViewModel by viewModels() | ||||||
|  |  | ||||||
|     private val fragments = arrayListOf<Fragment>() |     private val fragments = arrayListOf<Fragment>() | ||||||
|  |  | ||||||
|     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { |     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { | ||||||
| @ -33,7 +39,6 @@ class MyListsFragment : Fragment() { | |||||||
|         pagerAdapter = ScreenSlidePagerAdapter(this) |         pagerAdapter = ScreenSlidePagerAdapter(this) | ||||||
|         binding.pagerMyLists.adapter = pagerAdapter |         binding.pagerMyLists.adapter = pagerAdapter | ||||||
|  |  | ||||||
|         // TODO is position 0 always episodes? (and 1 always similar titles) |  | ||||||
|         TabLayoutMediator(binding.tabMyLists, binding.pagerMyLists) { tab, position -> |         TabLayoutMediator(binding.tabMyLists, binding.pagerMyLists) { tab, position -> | ||||||
|             tab.text = when(position) { |             tab.text = when(position) { | ||||||
|                 0 -> getString(R.string.my_list) |                 0 -> getString(R.string.my_list) | ||||||
| @ -43,15 +48,33 @@ class MyListsFragment : Fragment() { | |||||||
|             } |             } | ||||||
|         }.attach() |         }.attach() | ||||||
|  |  | ||||||
|         lifecycleScope.launch { |         viewLifecycleOwner.lifecycleScope.launch { | ||||||
|             val items = Crunchyroll.watchlist(50) |             viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||||||
|  |                 model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> | ||||||
|             MediaFragmentSimilar(items.toItemMediaList()).also { |                     when (uiState) { | ||||||
|                 fragments.add(it) |                         is MyListsFragmentViewModel.UiState.Normal -> bindUiStateNormal(uiState) | ||||||
|                 pagerAdapter.notifyItemInserted(fragments.indexOf(it)) |                         is MyListsFragmentViewModel.UiState.Loading -> bindUiStateLoading() | ||||||
|  |                         is MyListsFragmentViewModel.UiState.Error -> bindUiStateError(uiState) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun bindUiStateNormal(uiState: MyListsFragmentViewModel.UiState.Normal) { | ||||||
|  |         MediaFragmentSimilar(uiState.watchlistItems.toItemMediaList()).also { | ||||||
|  |             fragments.add(it) | ||||||
|  |             pagerAdapter.notifyItemInserted(fragments.indexOf(it)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun bindUiStateLoading() { | ||||||
|  |         // currently not used | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun bindUiStateError(uiState: MyListsFragmentViewModel.UiState.Error) { | ||||||
|  |         // currently not used | ||||||
|  |         Log.e(javaClass.name, "A error occurred while loading a UiState: ${uiState.message}") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ import kotlinx.coroutines.launch | |||||||
| import org.mosad.teapod.parser.crunchyroll.* | import org.mosad.teapod.parser.crunchyroll.* | ||||||
| import kotlin.random.Random | import kotlin.random.Random | ||||||
|  |  | ||||||
| class HomeViewModel : ViewModel()  { | class HomeViewModel : ViewModel() { | ||||||
|  |  | ||||||
|     private val WATCHLIST_LENGTH = 50 |     private val WATCHLIST_LENGTH = 50 | ||||||
|  |  | ||||||
|  | |||||||
| @ -0,0 +1,50 @@ | |||||||
|  | package org.mosad.teapod.ui.activity.main.viewmodel | ||||||
|  |  | ||||||
|  | import androidx.lifecycle.LifecycleCoroutineScope | ||||||
|  | import androidx.lifecycle.ViewModel | ||||||
|  | import androidx.lifecycle.viewModelScope | ||||||
|  | import kotlinx.coroutines.async | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
|  | import kotlinx.coroutines.launch | ||||||
|  | import org.mosad.teapod.parser.crunchyroll.Crunchyroll | ||||||
|  | import org.mosad.teapod.parser.crunchyroll.Item | ||||||
|  |  | ||||||
|  | class MyListsFragmentViewModel : ViewModel() { | ||||||
|  |  | ||||||
|  |     private val WATCHLIST_LENGTH = 50 | ||||||
|  |  | ||||||
|  |     private val uiState = MutableStateFlow<UiState>(UiState.Loading) | ||||||
|  |  | ||||||
|  |     sealed class UiState { | ||||||
|  |         object Loading : UiState() | ||||||
|  |         data class Normal( | ||||||
|  |             val watchlistItems: List<Item> | ||||||
|  |         ) : UiState() | ||||||
|  |         data class Error(val message: String?) : UiState() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         load() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { | ||||||
|  |         scope.launch { uiState.collect { collector(it) } } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun load() { | ||||||
|  |         viewModelScope.launch { | ||||||
|  |             uiState.emit(UiState.Loading) | ||||||
|  |             try { | ||||||
|  |                 // run the loading in parallel to speed up the process | ||||||
|  |                 val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(WATCHLIST_LENGTH).data } | ||||||
|  |                 uiState.emit( | ||||||
|  |                     UiState.Normal(watchlistJob.await()) | ||||||
|  |                 ) | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 uiState.emit(UiState.Error(e.message)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user