I have a Android Kotlin MVVM project, ViewModel is not being loaded at first lauch of the Fragment. But if i refresh it load data and show in the UI
HomeFragment.kt UI
class HomeFragment : Fragment() {
private val publicationViewModel: ListViewModel by viewModels()
private lateinit var magazineList: RecyclerView
private lateinit var paperList: RecyclerView
private var magazineListAdapter = PaperAdapter(arrayListOf())
private var paperListAdapter = PaperAdapter(arrayListOf())
private lateinit var paperSkeletonScreen: LinearLayout
private lateinit var magazineSkeletonScreen: LinearLayout
private var pullToRefresh: SwipeRefreshLayout? = null
var page: Int = 1
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
val view: View = LayoutInflater.from(container!!.context)
.inflate(R.layout.fragment_main_home, container, false)
findAll()
paperList = view.findViewById(R.id.today_newspaper_list)
paperList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false).apply {
paperList.layoutManager
}
paperList.apply {
adapter = paperListAdapter
}
paperSkeletonScreen = view.findViewById(R.id.newspaper_loader)
magazineList = view.findViewById(R.id.magazine_list)
magazineList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false).apply {
magazineList.layoutManager
}
magazineList.apply {
adapter = magazineListAdapter
}
magazineSkeletonScreen = view.findViewById(R.id.magazine_loader)
pullToRefresh = view.findViewById(R.id.refresh_page) as SwipeRefreshLayout
pullToRefresh?.setOnRefreshListener {
TrackerService.trackUserEvent(requireActivity(), "refresh_home")
refreshPage()
}
observeViewModelPublications()
return view
}
private fun observeViewModelPublications() {
try {
activity?.let { it ->
publicationViewModel.apply {
todayPapers.observe(it) { pubs ->
pubs?.let {
paperListAdapter.getPapers(it)
paperSkeletonScreen.visibility = View.GONE
}
}
}
publicationViewModel.apply {
magazine.observe(it) { pubs ->
pubs?.let { magazine ->
magazineListAdapter.getPapers(magazine)
magazineSkeletonScreen.visibility = View.GONE
}
}
}
publicationViewModel.viewModelLoadError.observe(it) { error ->
if (!error.isNullOrEmpty()) {
ToastHelper.showToast(requireActivity(), error, 1)
}
}
}
} catch (_: Exception) {
ToastHelper.showToast(
requireActivity(), requireActivity().getString(R.string.something_wrong), 1
)
}
}
private fun refreshPage() {
try {
pullToRefresh?.isRefreshing = false
arrayOf(
paperSkeletonScreen, magazineSkeletonScreen
).forEach { it.visibility = View.VISIBLE }
findAll()
Handler(Looper.getMainLooper()).postDelayed({
observeViewModelPublications()
}, 5000)
} catch (_: Exception) {
ToastHelper.showToast(requireActivity(), getString(R.string.something_wrong), 1)
}
}
private fun findAll() {
publicationViewModel.findPublications("Token", "KE", 1)
}
companion object {
fun newInstance(): MainHomeFragment {
return MainHomeFragment()
}
}
}
Interact with ApiService to make api call and updates the UI ListViewModel.kt
class ListViewModel : ViewModel() {
private val userData = ApiClient.ApiService()
private var job: Job? = null
// Publications
val magazine = MutableLiveData<PublicationModal>()
val todayPapers = MutableLiveData<List<PaperModal>>()
// Error Handling
val viewModelLoadError = MutableLiveData<String?>()
val loading = MutableLiveData<Boolean>()
fun findPublications(bearer: String?, country: String?, page: Int?) {
fetchTodayPapers(country)
fetchMagazine(country)
}
private fun fetchTodayPapers(country: String?) {
viewModelScope.launch {
when (val result =
RequestService().viewModelFetch(loading) { userData.findTodayPapers(country) }) {
is RequestService.Result.Success -> {
todayPapers.value = result.data?.data
viewModelLoadError.value = null
}
is RequestService.Result.Error -> onError(result.message)
}
}
}
private fun fetchMagazine(country: String?) {
viewModelScope.launch {
when (val result = RequestService().viewModelFetch(loading) {
userData.getTodayMagazine(
"2",
country
)
}) {
is RequestService.Result.Success -> {
magazine.value = result.data?.data
viewModelLoadError.value = null
}
is RequestService.Result.Error -> onError(result.message)
}
}
}
private fun onError(
message: String,
) {
viewModelLoadError.value = message
loading.value = false
}
override fun onCleared() {
super.onCleared()
job?.cancel()
}
}
Extension Service to make api calls scallfolds everything in it to remove duplicate code
in addition the val response = fetchDataFunction() returns null at start but when you refresh it return data.
RequestService.kt
class RequestService {
suspend fun <T> viewModelFetch(
loadingLiveData: MutableLiveData<Boolean>,
fetchDataFunction: suspend () -> Response<T>,
): Result<T> {
return try {
loadingLiveData.postValue(true)
val response = fetchDataFunction()
if (response.isSuccessful) {
loadingLiveData.postValue(false)
return Result.Success(response.body())
} else {
val responseBody = response.errorBody()?.toPrettyJson()
if (responseBody == null) {
loadingLiveData.postValue(true)
Result.Success(response.body())
} else {
val errorMessage =
responseBody.optString("message")
?: responseBody.optString("error")
loadingLiveData.postValue(false)
return Result.Error(errorMessage)
}
}
} catch (e: SocketTimeoutException) {
loadingLiveData.postValue(false)
return Result.Error("Request is taking too long to complete, try again later")
} catch (e: IOException) {
loadingLiveData.postValue(false)
return Result.Error("Network error, check your internet connection")
} catch (e: Exception) {
loadingLiveData.postValue(false)
return Result.Error("Network error, check your internet connection")
}
}
sealed class Result<out T> {
data class Success<out T>(val data: T?) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
}
ApiClient.kt calls my Api's and returns data
@GET("today")
suspend fun findTodayPapers(
@Query("country") country: String?,
): Response<PaperData>
@GET("latest")
suspend fun getTodayMagazine(
@Query("country") country: String?,
): Response<PaperData>
What is wrong in the above code, Any help