I want to abstract the retrofit call, so I don't need to write the same boiler plate code when I need a request.
The abstraction
open class NetworkCall<T> {
lateinit var call: Call<T>
var result: MutableLiveData<Resource<T>> = MutableLiveData()
fun makeCall(call: Call<T>) {
this.call = call
val callBackKt = CallBackKt<T>()
callBackKt.result.value = Resource.loading(null)
this.call.enqueue(callBackKt)
result = callBackKt.result
}
class CallBackKt<T> : Callback<T> {
var result: MutableLiveData<Resource<T>> = MutableLiveData()
override fun onFailure(call: Call<T>, t: Throwable) {
result.value = Resource.error()//APIError()
t.printStackTrace()
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
result.value = Resource.success(response.body())
}
else {
result.value = Resource.error()
}
}
}
}
Repository
class DetailsRepository(val application: Application) {
private val myListDao =
AppDatabase.getDatabase(application)?.MyListDao()
var movie: MutableLiveData<Resource<DetailsDTO>> = MutableLiveData()
fun getDetails(id: Int) {
val networkCall = NetworkCall<DetailsDTO>()
networkCall.makeCall(Apifactory.tmdbApi.getDetails(id))
movie = networkCall.result
}
}
ViewModel
class DetailsViewModel(application: Application) : AndroidViewModel(application) {
private val repository: DetailsRepository = DetailsRepository(application)
internal var movie: LiveData<Resource<DetailsDTO>> = repository.movie
fun insert(myListItem: MyListItem) {
repository.insert(myListItem)
}
fun fetchDetails(id: Int) {
repository.getDetails(id)
}
}
Activity
viewModel.movie.observe(this, Observer {
it?.apply {
detail_title.text = data?.title
}
})
Log with in every method says it's being called in right order and that the request is working fine. Althoght onChange is not being called.
fetchDetails called
getDetails called
makeCall called
onResponse successful, result.value.data = DetailsDTO(adult=false, backdrop_path=/y8lEIjYZCi2VFP4ixtHSn2klpth.jpg, ...
...
What would be done wrong?
Related
I am observing live data from the repository to view model but I am not getting any callback. Why is this so?
MyViewModel.kt
fun incrementPoints(userPoints: UserPoints): LiveData<NetworkResource<StatusResponse>> {
var callbackObserver = MutableLiveData<NetworkResource<StatusResponse>>()
IncrementPointsRepository.incrementPoints(userPoints).observeForever {
//not getting any callback here
callbackObserver.value = it
}
return callbackObserver
}
Repository.kt
object IncrementPointsRepository {
fun incrementPoints(userPoints: UserPoints): LiveData<NetworkResource<StatusResponse>> {
val callbackObserver = MutableLiveData<NetworkResource<StatusResponse>>()
val destinationService = ServiceBuilder.buildService(DestinationService::class.java)
val requestCall = destinationService.incrementPoints(userPoints)
requestCall.enqueue(object : Callback<StatusResponse> {
override fun onFailure(call: Call<StatusResponse>, t: Throwable) {
callbackObserver.value = NetworkResource.error(t)
}
override fun onResponse(call: Call<StatusResponse>, response: Response<StatusResponse>) {
//this is getting called
callbackObserver.value = NetworkResource.success(response)
}
})
return callbackObserver
}
}
incrementPoints function was called from another thread so it wasn't getting observed.
activity.runOnUiThread resolved this
I am trying to observe a progressBar and everything seems ok to me but its not working..
when it gets to the viewmodel its getting null value..
This is the repository:
val isLoadingProgressBarMutableLiveData = MutableLiveData<Boolean>()
fun getEmployeeListFromAPI(): MutableLiveData<List<Employee>> {
isLoadingProgressBarMutableLiveData.value = true
val apiRequest: APICallRequest = APIRequest.retrofitCallGetList
apiRequest.callEmployeeList().enqueue(object : Callback<EmployeesListResult?> {
override fun onResponse(
call: Call<EmployeesListResult?>,
response: Response<EmployeesListResult?>
) {
Log.e("onResponse1", "${isLoadingProgressBarMutableLiveData.value}")
if (response.isSuccessful) {
isLoadingProgressBarMutableLiveData.value = false
mutableListLiveData.value = response.body()?.getEmployeesListResult
Log.e("onResponse2", "${isLoadingProgressBarMutableLiveData.value}")
Log.e("onResponse", "Success!")
Log.e("Response:", "${response.body()}")
}
}
override fun onFailure(call: Call<EmployeesListResult?>, t: Throwable) {
Log.e("onFailure", "Failed getting list: ${t.message}")
isLoadingProgressBarMutableLiveData.value = false
}
})
return mutableListLiveData
}
fun getLoadingState() : MutableLiveData<Boolean>{
return isLoadingProgressBarMutableLiveData
}
"onResponse1" = true
"onResponse2" = false
but when I move it to the ViewModel I get null ...
This is the ViewModel:
class MainViewModel : ViewModel() {
fun getEmployeeListFromRepo() : LiveData<List<Employee>>{
return MainRepository().getEmployeeListFromAPI()
}
fun showProgressBar(): LiveData<Boolean> {
Log.e("Progress","ddd ${MainRepository().getLoadingState().value}")
return MainRepository().getLoadingState()
}
}
"ProgressBar" = is null
And in the activity:
mainViewModel.showProgressBar().observe(this, object : Observer<Boolean?> {
override fun onChanged(isLoading: Boolean?) {
Log.e("isLoadingProgressBar:", "Loading is...: $isLoading")
if (isLoading == true){
progressBar.visibility = View.VISIBLE
}else{
progressBar.visibility = View.GONE
}
}
})
Buddy - every time you do MainRepository() you're creating a new repository and accessing that. You should have one repository you're working with.
class MainViewModel : ViewModel() {
private val repository = MainRespository() // ONE repo
fun getEmployeeListFromRepo() : LiveData<List<Employee>>{
return repository.getEmployeeListFromAPI() // Get the live data to the ONE repo instead of creating a new one
}
fun showProgressBar(): LiveData<Boolean> {
// Print and return the value from the ONE respository instead of creating
// TWO new ones in this method
Log.e("Progress","ddd ${respository.getLoadingState().value}")
return respository.getLoadingState()
}
}
I am following the MVVM pattern Activity-->ViewModel ---> Repository . Repository is calling api and updated the LiveData. The value is of LiveData is also updated in ViewModel but its not reflecting on Activity. Please guide me where i am missing, Code is given below
Activity code:
class LoginWithEmailActivity : AppCompatActivity() {
private var loginViewModel: LoginViewModel? = null
private var binding: ActivityLoginWithEmailBinding? = null
private var btnLogin : Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding = DataBindingUtil.setContentView(this#LoginWithEmailActivity, R.layout.activity_login_with_email)
binding!!.setLifecycleOwner(this)
binding!!.setLoginViewModel(loginViewModel)
btnLogin = findViewById(R.id.btn_login)
loginViewModel!!.servicesLiveData!!.observe(this, Observer<LoginDataModel?> { serviceSetterGetter ->
val msg = serviceSetterGetter.success
Toast.makeText(this#LoginWithEmailActivity, ""+msg, Toast.LENGTH_SHORT).show()
Log.v("///LOGIN SUCCESS////",""+msg);
})
btnLogin!!.setOnClickListener {
loginViewModel!!.getUser()
}
}
ViewModel.kt
class LoginViewModel : ViewModel() {
var servicesLiveData: MutableLiveData<LoginDataModel>? = MutableLiveData()
fun getUser() {
servicesLiveData = MainActivityRepository.getServicesApiCall()
}
}
Repository.kt
object MainActivityRepository {
val serviceSetterGetter = MutableLiveData<LoginDataModel>()
fun getServicesApiCall(): MutableLiveData<LoginDataModel> {
val params = JsonObject()
params.addProperty("email", "xyz#gmail.com")
val call: Call<LoginDataModel> = ApiClient.getClient.getPhotos(params)
call.enqueue(object : Callback<LoginDataModel> {
#RequiresApi(Build.VERSION_CODES.N)
override fun onResponse(call: Call<LoginDataModel>?, response: Response<LoginDataModel>?) {
if (response != null) {
val data = response.body()
serviceSetterGetter?.postValue(data);
}
}
override fun onFailure(call: Call<LoginDataModel>?, t: Throwable?) {
}
})
return serviceSetterGetter
}
}
You subscribe to the LiveData in onCreate
loginViewModel!!.servicesLiveData!!.observe(this, Observer<LoginDataModel?> { serviceSetterGetter ->
val msg = serviceSetterGetter.success
Toast.makeText(this#LoginWithEmailActivity, ""+msg, Toast.LENGTH_SHORT).show()
Log.v("///LOGIN SUCCESS////",""+msg);
})
but then getUser creates a new reference
fun getUser() {
servicesLiveData = MainActivityRepository.getServicesApiCall()
}
The one you are subscribed to is not the same as the getUser liveData.
If you want to keep what you have mostly the same you need to use MediatorLiveData
Or just do
getUser().observe(this, Observer<LoginDataModel?> { serviceSetterGetter ->
val msg = serviceSetterGetter.success
Toast.makeText(this#LoginWithEmailActivity, ""+msg, Toast.LENGTH_SHORT).show()
Log.v("///LOGIN SUCCESS////",""+msg);
})
fun getUser(): LiveData<LoginDataModel> {
return MainActivityRepository.getServicesApiCall()
}
Everything working API request return response but, activity observer is triggered only first time with empty value and when response comes from request observer didn't see the changes.
Activity:
viewModel.jobQuestions.observe(this, Observer { list ->
list?.let {
jobQuestionsRv.apply {
setAdapterData(list)
}
}
})
ViewModel:
class JobQuestionsViewModel #Inject constructor(private val repository: Repository) : ViewModel() {
private val _jobQuestions = MutableLiveData<List<QuestionModel>>()
val jobQuestions: LiveData<List<QuestionModel>> = _jobQuestions
init {
_jobQuestions.postValue(repository.getQuestions())
}
}
Repository:
override fun getQuestions(): List<QuestionModel> {
var questionsList = ArrayList<QuestionModel>()
apiRequests?.questions()?.enqueue(object : retrofit2.Callback<List<QuestionModel>> {
override fun onResponse(
call: Call<List<QuestionModel>>?,
response: Response<List<QuestionModel>>
) {
response.body()?.let {
questionsList.addAll(response.body())
}
}
override fun onFailure(call: Call<List<QuestionModel>>?, t: Throwable?) {
questionsList.clear()
}
})
return questionsList
}
If you want to return LiveData from your repository, you can do following:
In repository, change type of questionsList to MutableLiveData and post the value whenever it's returned from callback:
override fun getQuestions(): LiveData<QuestionModel> {
val questionsList = MutableLiveData<List<QuestionModel>>()
apiRequests?.questions()?.enqueue(object : retrofit2.Callback<List<QuestionModel>> {
override fun onResponse(
call: Call<List<QuestionModel>>?,
response: Response<List<QuestionModel>>
) {
response.body()?.let {
questionsList.postValue(response.body())
}
}
override fun onFailure(call: Call<List<QuestionModel>>?, t: Throwable?) {
questionsList.postValue(emptyList())
}
})
return questionsList
}
In ViewModel, just call getQuestions():
class JobQuestionsViewModel #Inject constructor(private val repository: Repository) : ViewModel() {
val jobQuestions: LiveData<List<QuestionModel>> = repository.getQuestions()
}
If data that you are getting in observer throwing any exception then that observer will not called again until you set that observer again or preventing the exception.
I am a beginner in Kotlin. I need to send a variable parameter from my Activity to a Retrofit call.
This is my call in on Create of Detail Activity
override fun onCreate(savedInstanceState: Bundle?) {
//...
val id = intent.getStringExtra("id")
// Get the ViewMode
val mModel = ViewModelProviders.of(this).get(myObjectViewModel::class.java)
//Create the observer which updates the UI.
val myObjectByIdObserver = Observer<MyObject> { myObject->
//...
}
//Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getObjectById.observe(this, myObjectByIdObserver)
}
Here I insert value hardcode, I need the parameter received from the previous Activity.
class MyObjectViewModel : ViewModel() {
//this is the data that we will fetch asynchronously
var myObject: MutableLiveData<MyObject>? = null
val getMyObjectById: LiveData<MyObject>
get() {
if (myObject == null) {
myObject = MutableLiveData()
loadMyObjectById()
}
return myObject as MutableLiveData<MyObject>
}
private fun loadMyObjectById() {
val retrofit = Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(Api::class.java)
val call = api.myObjectById(100)
call.enqueue(object : Callback<MyObject> {
override fun onResponse(call: Call<MyObject>, response: Response<MyObject>) {
myObject!!.value = response.body()
}
override fun onFailure(call: Call<MyObject>, t: Throwable) {
var tt = t
}
})
}
My API:
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?): Call<MyObject>
}
You can do this by ``#Query``` annotation.
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?, #Query("a_param") aParam: String?): Call<MyObject>
}
Edited. I completely misread your intension.
What you need seems to be ViewModelProvider.NewInstanceFactory like
class MyObjectViewModel(val id: Int): ViewModel() {
class Factory(val id: Int) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyObjectViewModel(id) as T
}
}
}
then
val myViewModel = ViewModelProviders
.of(this, MyObjectViewModel.Factory(id))
.get(MyObjectViewModel::class.java)