I am developing an Android application for a university project that simulates a supermarket e-shop. The app interfaces with the REST API of the products database using Retrofit2 and Moshi, with the logic existing inside a viewmodel.
No matter what I try, it seems as though Retrofit fails to return anything when sending the GET request to the API for some reason inside viewModelScope.launch{ ... }
, but it works flawlessly if I move the code to MainActivity and run it inside a lifecycleScope.launch{ ... }
block.
ProductService.kt
interface:
interface ProductService {
@GET("product")
suspend fun getAllProducts(): Response<List<Product>>
@GET("product/{pId}")
suspend fun getProductById(@Path("pId") pId: Int): Response<Product>
@POST("product")
suspend fun createNewProduct(@Body post: POST): POST
}
WPAPI.kt
object singleton:
//retrofit & moshi setup
val moshi: Moshi = Moshi.Builder().add(com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory()).build()
val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
object WPAPI {
val retrofitService : ProductService by lazy {
Retrofit.Builder()
.baseUrl("http://[redacted]:8080/api/")
.addConverterFactory(MoshiConverterFactory.create(moshi)) //converter to use to parse the response
.client(client)
.build()
.create(ProductService::class.java) //class to use in order to construct the API service
}
}
getAllProducts()
function insideProductViewModel.kt
viewmodel:
fun getAllProducts() : LiveData<List<Product>> {
val result = MutableLiveData<List<Product>>()
viewModelScope.launch {
val response: Response<List<Product>> = try {
WPAPI.retrofitService.getAllProducts()
} catch (e: IOException) {
Log.e("ProductViewModel", "IOException", e)
return@launch
} catch (e: HttpException) {
Log.e("ProductViewModel", "HttpException", e)
return@launch
} catch (e: Exception) {
Log.wtf("ProductViewModel", "Exception", e)
return@launch
}
if (response.isSuccessful && response.body() != null) {
Log.i("ProductViewModel", "Response is good")
result.postValue(response.body()!!)
}
}
return result
}
The viewmodel's function is being called from MainActivity as follows:
val productsDBList : List<Product> = viewModel.getAllProducts().value!!
Stepping through the debugger when inside the ProductViewModel just jumps from WPAPI.retrofitService.getAllProducts()
inside the try-catch block to return result
at the bottom of getAllProducts()
, with no error being displayed in LogCat, up until a NullPointerException occurs due to the !!
operator in the above line. I thought that somehow the viewmodel's onCleared()
function was being called, but I tried overriding it and it wasn't called.
How I did it in MainActivity.kt
(using lifecycleScope
):
lifecycleScope.launch {
val response: Response<List<Product>> = try {
WPAPI.retrofitService.getAllProducts()
} catch (e: IOException) {
Log.e("ProductViewModel", "IOException", e)
return@launch
} catch (e: HttpException) {
Log.e("ProductViewModel", "HttpException", e)
return@launch
} catch (e: Exception) {
Log.wtf("ProductViewModel", "Exception", e)
return@launch
}
if (response.isSuccessful && response.body() != null) {
Log.i("LifecycleScope", "Response is good")
val pAdapter = ProductAdapter(response.body()!!)
productsRecView.adapter = pAdapter
productsRecView.layoutManager= LinearLayoutManager(this@MainActivity)
}
}
Can someone point me to the right direction? I know I'm probably missing something obvious here.
The problem is that you are trying to use live data in your viewmodel but dont actually use it correctly. calling viewModel.getAllProducts().value!!
does nothing because there will be no value in the live data yet so it has nothing to do with viewmodelScope vs lifecycleScope you are using them completely different.
you need to observe the live data with something like this
viewModel.getAllProducts().observe(this@MainActivity, Observer<List<Product>>{ products ->
val pAdapter = ProductAdapter(products)
productsRecView.adapter = pAdapter
productsRecView.layoutManager= LinearLayoutManager(this@MainActivity)
})
I didnt check this code for correctness but its something along those lines.
A better solution is to not use LiveData as thats old and more something for java, using kotlin you should really be using flows like StateFlow