Need help Kotlin Coroutines, Architecture Component and Retrofit - android

I'm trying to wrap my head around the mentioned components and I can't get it right. I want to do something very simple: Fetch data from the network and present it to the user. Currently am not yet caching it as am still learning new Coroutine features in Architecture components. Every time app loads I get an empty model posted, which seems weird.
My API is get hit fine and response is 200 which is OK.
Below is what I have attempted:
POJO
data class Profile(#SerializedName("fullname") val fullName : String.....)
Repository
class UserRepo(val context: Context, val api: Api) {
suspend fun getProfile(): Profile
{
val accessToken = ....
return api.getUserProfile(accessToken)
}
}
API
interface GatewayApi {
#GET("users/profile")
suspend fun getUserProfile(#Query("access-token") accessToken: String?): Profile
}
ViewModel
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val usersRepo = UserRepo(application.applicationContext, Apifactory.Api)
val userProfileData = liveData{
emit(usersRepo.getProfile())
}
fun getProfile() = viewModelScope.launch {
usersRepo.getProfile()
}
}
Finally my fragment's relevant code
val viewModel = ViewModelProviders.of(activity!!).get(UserViewModel::class.java)
viewModel.userProfileData.observe(this, Observer<UserProfile> {
//it is having nulls
})
//trigger change
viewModel.getProfile()

So I added HTTP requests and responses (thanks to #CommonsWare for pointing that out) and it happened I had used a different model than I was supposed to use. The correct model that mapped the JSON response was ProfileResponse and as you can see in my posted code, I used Profile instead. So all fields were empty as Gson could not correctly serialize JSON into Profile object.
All the credit goes to #CommonsWare for pointing that out in comment.

Related

How I can access JSON data for two brackets. - Retrofit2

I am working with an api that displays currency prices. Although I tried many times, I could not find a way to follow the JSON map. So I can't access the data.
My JSON format below
{"USD":{"satis":"18.6391","alis":"18.6268","degisim":"0.07"},"EUR":{"satis":"19.2998","alis":"19.2894","degisim":"0.57"}
my api interface code below:
interface CurrencyAPI {
#GET("embed/para-birimleri.json")
fun getData(): Call<List<CurrencyModel>>
}
my main class
private fun loadData()
{
val retrofit=Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service=retrofit.create(CurrencyAPI::class.java)
val call=service.getData()
call.enqueue(object :Callback<List<CurrencyModel>>
{
override fun onResponse(
call: Call<List<CurrencyModel>>,
response: Response<List<CurrencyModel>>
) {
if (response.isSuccessful)
{
response.body()?.let {
currencyModels=ArrayList(it)
}
}
}
override fun onFailure(call: Call<List<CurrencyModel>>, t: Throwable) {
t.printStackTrace()
}
})
}
my model class
import com.google.gson.annotations.SerializedName
data class CurrencyModel(
#SerializedName("satis")
val selling:String,
#SerializedName("alis")
val buying:String,
#SerializedName("degisim")
val change:String
)
I tried data via list but I Could not get data.
Each time you see { in JSON it represents the start of a new object, which means that you have a top level object that has multiple values (in your case USD and EUR) that are each objects themselves. You've created a class representing these inner objects correctly, but you are incorrectly trying to deserialize the entire JSON body as a list/array rather than an object. Now, there are a few things to consider when deciding how to deserialize the entire JSON body:
Do you know all of the possible keys ahead of time?
Will these keys stay the same for the foreseeable future?
Are these keys static or dynamic?
If you answered no to either of the first 2 questions, or answered dynamic to the last one, then you won't want to make a class representing the object and use a Map<String, CurrencyModel> instead. If you answered yes to both of the first 2 questions, and the answer to the last one was static, then you can make a class to represent the entire body, where each property of the class has type CurrencyModel, though you can still use the map above.

How to use the keyword also in kotlin android

Am learning android kotlin follow this:
https://developer.android.com/topic/libraries/architecture/viewmodel#kotlin
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers(it)
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
Dont know how to write the fun loadUsers()
Here is my User:
class User {
constructor(name: String?) {
this.name = name
}
var name:String? = null
}
If dont use the keyword 'also' , i know how to do it.
But if use 'also' , it seems not work.
Here is how i try to write the fun loadUsers:
private fun loadUsers( it: MutableLiveData<List<User>>){
val users: MutableList<User> = ArrayList()
for (i in 0..9) {
users.add(User("name$i"))
}
it = MutableLiveData<List<User>>(users)
}
Error tips near it : Val cant be ressigned
Part 1: According to the Kotlin documentation, also provides the object in question to the function block as a this parameter. So, every function call and property object you access is implied to refer to your MutableLiveData<List<User>>() object. also returns this from the function block when you are done.
Thus, another way of writing your MutableLiveData<> would be like this:
val users = MutableLiveData<List<User>>()
users.loadUsers()
Part 2: As far as how to implement loadUsers(), that is a separate issue (your question is not clear). You can use Retrofit + RxJava to load the data asynchronously, and that operation is totally outside of the realm of ViewModel or also.
Part 3: With your approach, you have conflicting things going on. Instead of doing a loadUsers() from your lazy {} operation, I would remove your lazy {} operation and create a MutableLiveData<> directly. Then, you can load users later on and update the users property any time new data is loaded. Here is a similar example I worked on a while ago. It uses state flows, but the idea is similar. Also use a data class to model the User instead of a regular class. Another example.
It is solved change to code:
private fun loadUsers( it: MutableLiveData<List<User>>){
val users: MutableList<User> = ArrayList()
for (i in 0..9) {
users.add(User("name$i"))
}
it.value = users
}
it can't be reassigned , but it.value could .

What is the difference between emit and emitSource with LiveData ? ( as in REAL time USE-CASE )

emit accepts the data class whereas emitSource accepts LiveData<T> ( T -> data ). Considering the following example :- I have two type of calls :-
suspend fun getData(): Data // returns directly data
and the other one ;
suspend fun getData(): LiveData<Data> // returns live data instead
For the first case i can use:-
liveData {
emit(LOADING)
emit(getData())
}
My question : Using the above method would solve my problem , WHY do we need emitSource(liveData) anyway ?
Any good use-case for using the emitSource method would make it clear !
As you mentioned, I don't think it solves anything in your stated problem, but I usually use it like this:
If I want to show cached data to the user from the db while I get fresh data from remote, with only emit it would look something like this:
liveData{
emit(db.getData())
val latest = webService.getLatestData()
db.insert(latest)
emit(db.getData())
}
But with emitSource it looks like this:
liveData{
emitSource(db.getData())
val latest = webService.getLatestData()
db.insert(latest)
}
Don't need to call emit again since the liveData already have a source.
From what I understand emit(someValue) is similar to myData.value = someValue whereas emitSource(someLiveValue) is similar to myData = someLiveValue. This means that you can use emit whenever you want to set a value once, but if you want to connect your live data to another live data value you use emit source. An example would be emitting live data from a call to room (using emitSource(someLiveData)) then performing a network query and emitting an error (using emit(someError)).
I found a real use-case which depicts the use of emitSource over emit which I have used many times in production now. :D The use-case:
Suppose u have some user data (User which has some fields like userId, userName ) returned by some ApiService.
The User Model:
data class User(var userId: String, var userName: String)
The userName is required by the view/activity to paint the UI. And the userId is used to make another API call which returns the UserData like profileImage , emailId.
The UserData Model:
data class UserData(var profileImage: String, var emailId: String)
This can be achieved internally using emitSource by wiring the two liveData in the ViewModel like:
User liveData -
val userLiveData: LiveData<User> = liveData {
emit(service.getUser())
}
UserData liveData -
val userDataLiveData: LiveData<UserData> = liveData {
emitSource(userLiveData.switchMap {
liveData {
emit(service.getUserData(it.userId))
}
})
}
So, in the activity / view one can ONLY call getUser() and the getUserData(userId) will be automatically triggered internally via switchMap.
You need not manually call the getUserData(id) by passing the id.
This is a simple example, imagine there is a chain of dependent-tasks which needs to be executed one after the other, each of which is observed in the activity. emitSource comes in handy
With emitSource() you can not only emit a single value, but attach your LiveData to another LiveData and start emitting from it. Anyway, each emit() or emitSource() call will remove the previously added source.
var someData = liveData {
val cachedData = dataRepository.getCachedData()
emit(cachedData)
val actualData = dataRepository.getData()
emitSource(actualData)
}
The activity that’s observing the someData object, will quickly receive the cached data on the device and update the UI. Then, the LiveData itself will take care of making the network request and replace the cached data with a new live stream of data, that will eventually trigger the Activity observer and update the UI with the updated info.
Source: Exploring new Coroutines and Lifecycle Architectural Components integration on Android
I will like share a example where we use "emit" and "emitsource" both to communicate from UI -> View Model -> Repository
Repository layer we use emit to send the values downstream :
suspend fun fetchNews(): Flow<Result<List<Article>>> {
val queryPath = QueryPath("tata", apikey = AppConstant.API_KEY)
return flow {
emit(
Result.success(
openNewsAPI.getResponse(
"everything",
queryPath.searchTitle,
queryPath.page,
queryPath.apikey
).articles
)
)
}.catch { exception ->
emit(Result.failure(RuntimeException(exception.message)));
}
}
ViewModel layer we use emitsource to pass the live data object to UI for subscriptions
val loader = MutableLiveData<Boolean>()
val newsListLiveData = liveData<Result<List<Article>>> {
loader.postValue(true)
emitSource(newRepo.fetchNews()
.onEach {
loader.postValue(false)
}
.asLiveData())
}
UI Layer - we observe the live data emitted by emitsource
viewModel.newsListLiveData.observe(viewLifecycleOwner, { result ->
val listArticle = result.getOrNull()
if (result.isSuccess && listArticle != null) {
setupList(binding.list, listArticle)
} else {
Toast.makeText(
appContext,
result.exceptionOrNull()?.message + "Error",
Toast.LENGTH_LONG
).show()
}
})
We convert Flow observable to LiveData in viewModel

Android MVVM: how can I update data in a Repository from another Repository, when same data is part of response from different server endpoints?

I'm trying to move Android project to MVVM. Two different repositories get same data response, when calling different server endpoints. What is the right way to keep both up-to-date?
There are different server endpoints returning "UserData": "/get_user_data" and "/get_user_statistics". I need to update data in "UserRepository", when making request in "UserStatisticsRepository" (which returns UserData as well).
Should I inject both Repositories in a ViewModel and then set the "UserData" via ViewModel to "UserRepository"? It just feels to me not really right way to set data to a Repository from a ViewModel...
Let's say, I have:
data class UserData(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String
)
and
data class UserStatisticData(
#SerializedName("id") val id: Int,
#SerializedName("active_users_count") val activeUsersCount: Int,
#SerializedName("users") val users: List<UserData>
)
and
class UserStatisticRepository(...) {
...
suspend fun makeUserStatisticDataRequest(){
withContext(Dispatchers.IO) {
val networkResponse = myAppAPI?.getUserStatisticData(params)
try {
if (networkResponse!!.isSuccessful) {
_userStatisticData.value = networkResponse.body()?.userStatisticData
// TODO: Should I set UserData (networkResponse.body()?.userData) to UserRepository here???
// How can I access UserRepository?
} else {
throw UserStatisticDataResponseError(Exception("Response body error..."))
}
} catch (e: Exception) {
throw UserStatisticDataResponseError(Exception("Network error..."))
}
}
}
...
}
I am not sure how your projwct is set up but If I was in this kind of situation I would :
Either have a single state that is mutated by both of the repository, make it reactive. Both of the repostitory can take that state as argument when being constructed and they can also mutate the state.
A reactive database that is mutated by both the calls. i.e. whenever any new UserData entity is fetched, its written to databse. When we need UserData we listen it directly from Reactive Database like "Room" instead of those API endpoints directly.
When to do what?
I would choose the first option if I did not have to persist the fetched data & I am cool with losing all the data once app is closed where as If I wanted the data from those endpoints to survive App restart the I would go for the second options.
For the first option the code may look like this :
data class UserDataSource(val userDataList : List<UserData>)
class UserDataRepo(val subject : PublishSubject<UserDataSource>){
UserdataService.getUserData(userID).subscribe{
// Do calculations to find the old list of user and edit or update the new user
subject.onNext(UserDataSource(newUserList))
}
}
class UserStatRepo(subject : PublishSubject<UserDataSource>){
UserdataService.getUserStat().subscribe{
// Do calculations to find the old list of user and edit or update the new user
subject.onNext(UserDataSource(newUserList))
}}
In Ui subscribe to the userDataSource subject..
class UserDataViewModel{
val userDataSource = PublishSubject.create<userDataSource>()
val userDataRepository = UserDataRepo (userDataSource)
val userDataRepository = UserStatRepo (userDataSource)
}

How can I generate mock data object for retrofit response automatically?

I am newbie to writing a test code as an Android developer.
I am using the Kotlin and Retrofit in my android app.
I have a retrofit interface like below:
#GET("/3/movie/{movieId}")
fun getMovie(#Query("api_key") apiKey: String, #Path("movieId") movieId: String): Single<TmdbMovieResp>
The response is "TmdbMovieResp".
And my test code is :
`when`(mockApi.getMovie(mockApiKey, "id")).thenReturn(Single.just(mockMovieResp))
This means I should make the "mockMovieResp".
But the "TmdbMovieResp" has too many member variables.
I can make it, but it's too boring!
And in this case it just one test.
If I have more methods and response types, I should do similar task again and again!
Is there any cool solution?
Here is different approach. You can use https://github.com/andrzejchm/RESTMock
More info by the link. Usage is very simple:
RESTMockServer.whenGET(pathContains("x/y/z/"))
.thenReturnString("your-response-json");
One thing you can do by using your Model classes in your Test method.
As you are using Retrofit you must have some converter like Moshi, GSON for response handling. If you have model classes then use those class for response mocking like below.
val success = TmdbMovieResp() //Response Model class
Then mock the rest api call with the success result like below.
`when`(mockApi.getMovie(mockApiKey, "id")).thenReturn(Single.just(success))
Instead of Mockito, use MockK for your mocking, then utilize relaxed mocks.
class MyClass {
fun foo() : ComplexObject { ... }
}
#Test
fun `test foo`() {
val myClass = spyk(MyClass())
val complex : ComplexObject = mockk(relaxed = true)
every { myClass.foo() } returns complex
assertEquals(complex, myClass.foo())
println(complex.someIntProperty) // prints 1
}
Relaxed mockks return canned data for any value or function, so that you don't need to specify all of those individual properties when they don't matter.

Categories

Resources