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
Related
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 have a project that part of it uses MVVM Architecture ,,I use a repo to get the data from backend like this :
private val spotlights: MutableLiveData<List<SpotlightModel>> = MutableLiveData()
fun getSpotLight(segmentId: String, segmentType: String): MutableLiveData<List<SpotlightModel>>? {
mProvider.getSpotLight(
segmentType = segmentType,
adType = TYPE_SPOT_LIGHT,
segmentId = segmentId,
topics = application.profileManger.topicsIds,
publishers = application.profileManger.publisherIds,
debug = application.debugFlag,deviceId = application.idfa)
.enqueue(object : Callback<List<SpotlightModel>> {
override fun onResponse(call: Call<List<SpotlightModel>>, response: Response<List<SpotlightModel>>) {
if (response.isSuccessful) {
spotlights.postValue(response.body())
}
}
override fun onFailure(call: Call<List<SpotlightModel>>, t: Throwable) {
spotlights.postValue(null)
}
})
return spotlights
}
Then in the viewmodel I do this ::
class SpotlightViewModel(val application: NewsApplication) :ViewModel() {
var segmentType: String=""
var segmentId:String=""
val repository=SpotlightRepository(application)
var spotslightsLiveData=repository.getSpotLight(segmentId,segmentType)!!
//if I didn't add this method the data doesn't comeback
fun getSpotLights() {
spotslightsLiveData= repository.getSpotLight(segmentId,segmentType)!!
}}
I observe it like this
fun getSpotlights(s_id:String, s_type:String){
spotlightViewModel?.segmentId=s_id
spotlightViewModel?.segmentType=s_type
spotlightViewModel?.spotslightsLiveData?.removeObservers(viewLifecycleOwner)
spotlightViewModel?.spotslightsLiveData?.observe(viewLifecycleOwner, Observer
{ spotlightList ->
if (spotlightList.isNullOrEmpty()) {
newNewsAdapter?.spotLights = null
newNewsAdapter?.notifyItemChanged(0)
}
else if (spotlightList.isNotEmpty()) {
newNewsAdapter?.spotLights = spotlightList
newNewsAdapter?.notifyItemChanged(0)
}
})
spotlightViewModel?.getSpotLights() }
This code works fine but observer is called many times..also can I use a switch map here if yes how?? Thanks in advance :)
You can use switchMap:
val resultLiveData = paramsLiveData.switchMap { params ->
getLiveDataBy(params)
}
my data is fetched only when it is created...im using viewmodel...when press back button it doesnt update the previous data..onresume is not working in this...
i refered this but none of those helped--> Reacting to activity lifecycle in ViewModel
i need help
thanks in advance
activity:--
class MyAccount : BaseClassActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.myaccount)
var mActionBarToolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbartable);
setSupportActionBar(mActionBarToolbar);
setEnabledTitle()
val resetbutton=findViewById<Button>(R.id.resetpwd)
resetbutton.setOnClickListener {
val i=Intent(applicationContext,
ResetPasswordActivity::class.java)
startActivity(i)
}
val editbutton=findViewById<Button>(R.id.editdetail)
editbutton.setOnClickListener {
val i=Intent(applicationContext, EditProfile::class.java)
startActivity(i)
}
hello()
}
override fun onResume() {
super.onResume()
hello()
}
fun hello(){
val first_name = findViewById<TextView>(R.id.firstname)
val last_name = findViewById<TextView>(R.id.lastname)
val emailuser = findViewById<TextView>(R.id.emailuser)
val phone_no = findViewById<TextView>(R.id.phone_no)
val birthday = findViewById<TextView>(R.id.birthday)
val image=findViewById<ImageView>(R.id.imageprofile)
val model = ViewModelProvider(this)[MyAccountViewModel::class.java]
model.viewmodel?.observe(this, object : Observer<My_account_base_response> {
override fun onChanged(t: My_account_base_response?) {
first_name.setText(t?.data?.user_data?.first_name)
last_name.setText(t?.data?.user_data?.last_name)
emailuser.setText(t?.data?.user_data?.email)
phone_no.setText(t?.data?.user_data?.phone_no).toString()
birthday.setText(t?.data?.user_data?.dob).toString()
Glide.with(applicationContext).load(t?.data?.user_data?.profile_pic)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.ic_launcher_foreground)
.into(image)
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
true
}
else -> super.onOptionsItemSelected(item)
}
}}
viewmodel:--
class MyAccountViewModel(context: Application) :AndroidViewModel(context),LifecycleObserver{
private var MyAccountViewModels: MutableLiveData<My_account_base_response>? = null
val viewmodel: MutableLiveData<My_account_base_response>?
get() {
if (MyAccountViewModels == null) {
MyAccountViewModels = MutableLiveData<My_account_base_response>()
loadviewmodel()
}
return MyAccountViewModels
}
private fun loadviewmodel(){
val token :String = SharedPrefManager.getInstance(getApplication()).user.access_token.toString()
RetrofitClient.instance.fetchUser(token)
.enqueue(object : Callback<My_account_base_response> {
override fun onFailure(call: Call<My_account_base_response>, t: Throwable) {
Log.d("res", "" + t)
}
override fun onResponse(
call: Call<My_account_base_response>,
response: Response<My_account_base_response>
) {
var res = response
if (res.body()?.status == 200) {
MyAccountViewModels!!.value = response.body()
} else {
try {
val jObjError =
JSONObject(response.errorBody()!!.string())
Toast.makeText(getApplication(),
jObjError.getString("user_msg"),
Toast.LENGTH_LONG).show()
} catch (e: Exception) {
Log.e("errorrr", e.message)
}
}
}
})
}}
There are bunch of things wrong here, so let me provide you refactored code and explanation as much as I would be able to..
Activity:
class MyAccount : BaseClassActivity() {
private val mActionBarToolbar by lazy { findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbartable) }
private val resetbutton by lazy { findViewById<Button>(R.id.resetpwd) }
private val editbutton by lazy { findViewById<Button>(R.id.editdetail) }
private val first_name by lazy { findViewById<TextView>(R.id.firstname) }
private val last_name by lazy { findViewById<TextView>(R.id.lastname) }
private val emailuser by lazy { findViewById<TextView>(R.id.emailuser) }
private val phone_no by lazy { findViewById<TextView>(R.id.phone_no) }
private val birthday by lazy { findViewById<TextView>(R.id.birthday) }
private val image by lazy { findViewById<ImageView>(R.id.imageprofile) }
lateinit var model: MyAccountViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.myaccount)
setSupportActionBar(mActionBarToolbar)
setEnabledTitle()
model = ViewModelProvider(this)[MyAccountViewModel::class.java]
resetbutton.setOnClickListener {
val i = Intent(applicationContext, ResetPasswordActivity::class.java)
startActivity(i)
}
editbutton.setOnClickListener {
val i = Intent(applicationContext, EditProfile::class.java)
startActivity(i)
}
model.accountResponseData.observe(this, object : Observer<My_account_base_response> {
override fun onChanged(t: My_account_base_response?) {
first_name.setText(t?.data?.user_data?.first_name)
last_name.setText(t?.data?.user_data?.last_name)
emailuser.setText(t?.data?.user_data?.email)
phone_no.setText(t?.data?.user_data?.phone_no).toString()
birthday.setText(t?.data?.user_data?.dob).toString()
Glide.with(applicationContext)
.load(t?.data?.user_data?.profile_pic)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.ic_launcher_foreground)
.into(image)
}
})
}
override fun onResume() {
super.onResume()
model.loadAccountData()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
Few notes on your activity class:
You don't need to findViewById everytime, just do it once during onCreate or do it lazily. (FYI consider using kotlin synthetics or view binding or data binding)
Initialize your viewModel during onCreate method only. (That's the best way to do it)
Also observer your LiveData from ViewModel once, it should be also from the onCreate as it's the entry point to the activity and apart from config changes this method called only once. So, it's safe to observe it over there rather than during onResume which will be called multiple times during activity lifecycle. (The main issue your code wasn't working, so as a fix you only call your API method from ViewModel during resume)
ViewModel:
class MyAccountViewModel(context: Application) : AndroidViewModel(context) {
private val _accountResponseData = MutableLiveData<My_account_base_response?>()
val accountResponseData: MutableLiveData<My_account_base_response?>
get() = _accountResponseData
init {
loadAccountData()
}
fun loadAccountData() {
val token: String = SharedPrefManager.getInstance(getApplication()).user.access_token.toString()
RetrofitClient.instance.fetchUser(token)
.enqueue(object : Callback<My_account_base_response> {
override fun onFailure(call: Call<My_account_base_response>, t: Throwable) {
Log.d("res", "" + t)
_accountResponseData.value = null
}
override fun onResponse(
call: Call<My_account_base_response>,
response: Response<My_account_base_response>
) {
var res = response
if (res.body()?.status == 200) {
_accountResponseData.value = response.body()
} else {
try {
val jObjError =
JSONObject(response.errorBody()!!.string())
Toast.makeText(
getApplication(),
jObjError.getString("user_msg"),
Toast.LENGTH_LONG
).show()
} catch (e: Exception) {
Log.e("errorrr", e.message)
}
}
}
})
}
}
Don't make initial API call along with LiveData creation, it's okay to do in most of cases but if you're updating LiveData on response of that call then it's good to make it separately like during init block.
It's good practice not to allow Ui (Activity/Fragments) to modify LiveDatas of ViewModel directly. So, that's good sign you're following such pattern by having private MutableLiveData exposed as public LiveData, but do it correctly as suggested.
Side note: Your view model doesn't need to be LifecycleObserver. LifecycleObserver is used for some custom class/component which needs to be managed by their self by silently observing/depending on activity lifecycle independently. That's not the use case of ViewModel.
The only thing that I found why your code wasn't working correctly is because you were creating & observing ViewModel & LiveData over & over again as new objects from onResume method where you called hello() method.
Let me know if something don't make sense or missing.
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 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?