When we have a liveData as below, we cannot _liveData.value++, as the value is nullable.
class MainViewModel(savedStateHandle: SavedStateHandle): ViewModel() {
private val _liveData: MutableLiveData<Int> =
savedStateHandle.getLiveData("SomeKey", 0)
val liveData: LiveData<Int> = _liveData
fun triggerLiveData() {
_liveData.value++
}
}
The article https://proandroiddev.com/improving-livedata-nullability-in-kotlin-45751a2bafb7 provide a solution, i.e.
#Suppress("UNCHECKED_CAST")
class SafeMutableLiveData<T>(value: T) : LiveData<T>(value) {
override fun getValue(): T = super.getValue() as T
public override fun setValue(value: T) = super.setValue(value)
public override fun postValue(value: T) = super.postValue(value)
}
But that didn't support savedState.
How can we get a non-nullable LiveData that also has savedstate?
I have a solution that duplicate the data and don't look that elegant
#Suppress("UNCHECKED_CAST")
class SafeMutableLiveData<T: Any>(private val mutableLiveData: MutableLiveData<T>) :
MutableLiveData<T>(mutableLiveData.value) {
override fun getValue(): T = mutableLiveData.value as T
override fun setValue(value: T) {
super.setValue(value)
mutableLiveData.value = value
}
override fun postValue(value: T) {
super.postValue(value)
mutableLiveData.postValue(value)
}
}
The usage is as below
class MainViewModel(savedStateHandle: SavedStateHandle): ViewModel() {
private val _liveData: SafeMutableLiveData<Int> =
SafeMutableLiveData(savedStateHandle.getLiveData("Something", 0))
val liveData: LiveData<Int> = _liveData
fun triggerLiveData() {
_liveData.value++
}
}
Related
I have such issue:
Two fragments: A and B, which inject viewModel, but for some reason I have result of LiveData in both of my fragments.
How can I avoid triggering live data pushing me old value? How to reset liveData somehow or ignore old values? Thanks.
In both of them I am listening to LiveData changes like this:
#AndroidEntryPoint
class LoginFragment : MyBaseDebugFragment(R.layout.spinner_layout) {
#Inject
private val viewModel: AuthorizationViewModel by activityViewModels()
{
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
viewModel.loginActionLD.observe(viewLifecycleOwner) { loginStatus ->
//Do something regarding the value of login status.
}
viewModel.DoLogin()
}
#HiltViewModel
class AuthorizationViewModel #Inject constructor(
private val login: LoginUseCase,
private val logout: LogoutUseCase,
#Dispatcher.IO private val ioDispatcher: CoroutineDispatcher,
#Scope.Application private val externalScope: CoroutineScope,
) : ViewModel() {
private val _loginActionLD = MutableLiveData<LoginAction>()
val loginActionLD: LiveData<LoginAction> = _loginActionLD
fun DoLogin(from: AuthRequest) {
launchOnMyExternalScope {
_loginActionLD.postValue(login().toLoginAction(from))
}
}
private fun launchOnMyExternalScope(block: suspend CoroutineScope.() -> Unit) =
externalScope.launch(ioDispatcher, block = block)
}
}
#Module
#InstallIn(SingletonComponent::class)
object CoroutineScopeModule {
#Singleton
#Scope.Application
#Provides
fun provideApplicationScope(#Dispatcher.IO ioDispatcher: CoroutineDispatcher): CoroutineScope =
CoroutineScope(SupervisorJob() + ioDispatcher)
}
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class Scope {
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class Application
}
There is a handy class SingleLiveEvent that you can use instead of LiveData in your ViewModel class to send only new updates after subscription.
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
fun call() {
postValue(null)
}
}
This LiveData extension only calls the observable if there's an explicit call to setValue() or call().
Here is what helped me to avoid LiveData to trigger twice it's handler. This code is tested carefully:
open class LiveEvent<T> : MediatorLiveData<T>() {
private val observers = ArraySet<OneTimeObserver<in T>>()
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
val wrapper = OneTimeObserver(observer)
observers.add(wrapper)
super.observe(owner, wrapper)
}
#MainThread
override fun observeForever(observer: Observer<in T>) {
val wrapper = OneTimeObserver(observer)
observers.add(wrapper)
super.observeForever(wrapper)
}
#MainThread
override fun removeObserver(observer: Observer<in T>) {
if ((observer is OneTimeObserver && observers.remove(observer)) || observers.removeIf { it.observer == observer }) {
super.removeObserver(observer)
}
}
#MainThread
override fun setValue(t: T?) {
observers.forEach { it.newValue() }
super.setValue(t)
}
private class OneTimeObserver<T>(val observer: Observer<T>) : Observer<T> {
private var handled = AtomicBoolean(true)
override fun onChanged(t: T?) {
if (handled.compareAndSet(false, true)) observer.onChanged(t)
}
fun newValue() {
handled.set(false)
}
}
}
And then instead of such code:
private val _loginAction = MutableLiveData<LoginAction>()
val loginActionLD: LiveData<LoginAction> = _loginAction
I have used this code:
private val _loginAction = LiveEvent<LoginAction>()
val loginActionLD: LiveData<LoginAction> = _loginAction
I have following base class :
abstract class BaseViewModel<T, R>(private val schedulerProvider: BaseSchedulerProvider) :
ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val _liveData = MutableLiveData<Resource<T>>()
val liveData: LiveData<Resource<T>>
get() = _liveData
protected abstract val requestObservable: Observable<R>
protected abstract fun getSuccessResult(it: R): T
fun sendRequest() {
_liveData.value = Resource.Loading()
composeObservable { requestObservable }
.subscribe({
_liveData.postValue(Resource.Success(getSuccessResult(it)))
}) {
_liveData.postValue(Resource.Failure(it.localizedMessage))
Timber.e(it)
}.also { compositeDisposable.add(it) }
}
}
And here is child class implementation :
class MainViewModel(
api: PokemonService,
schedulerProvider: BaseSchedulerProvider
) : BaseViewModel<List<Pokemon>, List<NamedResponseModel>>(schedulerProvider) {
override val requestObservable: Observable<List<NamedResponseModel>> =
api.getPokemonList(LIMIT).map { it.results }
override fun getSuccessResult(it: List<NamedResponseModel>): List<Pokemon> = it.asDomainModel()
init {
sendRequest()
}
}
As you see I put init block in child classes to sendRequest() which is a redundant. If I move init block to parent class, it will crash since api is null because init block of parent is called before constructor of child.
Is there any solution to move sendRequest() to parent and avoid redundant in child classes?
Source code can be found : https://github.com/AliRezaeiii/Pokemon
I think you need to change the design of your inheritance. To get the child items to be executed in the parent's initialization, you need to pass the object to the parent constructor.
Here is an example:
abstract class Base(protected val name: String) {
init {
println(name)
}
}
class CBase(private val s: String) : Base(s) {}
fun main() {
CBase("Hello");
}
In your case, which I haven't tested yet:
abstract class BaseViewModel<T, R>(
private val schedulerProvider: BaseSchedulerProvider,
protected val requestObservable: Observable<R>):
ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val _liveData = MutableLiveData<Resource<T>>()
val liveData: LiveData<Resource<T>>
get() = _liveData
protected abstract fun getSuccessResult(it: R): T
fun sendRequest() {
_liveData.value = Resource.Loading()
composeObservable { requestObservable }
.subscribe({
_liveData.postValue(Resource.Success(getSuccessResult(it)))
}) {
_liveData.postValue(Resource.Failure(it.localizedMessage))
Timber.e(it)
}.also { compositeDisposable.add(it) }
}
init {
sendRequest()
}
}
class MainViewModel(
api: PokemonService,
schedulerProvider: BaseSchedulerProvider
) : BaseViewModel<List<Pokemon>, List<NamedResponseModel>>(
schedulerProvider,
api.getPokemonList(LIMIT).map { it.results }
) {
override fun getSuccessResult(it: List<NamedResponseModel>): List<Pokemon> = it.asDomainModel()
}
Here, you can still access the variable requestObservable at the parent's contructor because it is initialized at the constructor parameter, not as an abstract property.
Let me know how it works for you.
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.
MainActivity
class MainActivity : AppCompatActivity() {
#Inject
lateinit var mainViewModelFactory: mainViewModelFactory
private lateinit var mainActivityBinding: ActivityMainBinding
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainActivityBinding = DataBindingUtil.setContentView(
this,
R.layout.activity_main
)
mainActivityBinding.rvmainRepos.adapter = mainAdapter
AndroidInjection.inject(this)
mainViewModel =
ViewModelProviders.of(
this#MainActivity,
mainViewModelFactory
)[mainViewModel::class.java]
mainActivityBinding.viewmodel = mainViewModel
mainActivityBinding.lifecycleOwner = this
mainViewModel.mainRepoReponse.observe(this, Observer<Response> {
repoList.clear()
it.success?.let { response ->
if (!response.isEmpty()) {
// mainViewModel.saveDataToDb(response)
// mainViewModel.createWorkerForClearingDb()
}
}
})
}
}
MainViewModelFactory
class MainViewModelFactory #Inject constructor(
val mainRepository: mainRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(mainViewModel::class.java) -> mainViewModel(
mainRepository = mainRepository
)
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
MainViewModel
class MainViewModel(
val mainRepository: mainRepository
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
val mainRepoReponse = MutableLiveData<Response>()
val loadingProgress: MutableLiveData<Boolean> = MutableLiveData()
val _loadingProgress: LiveData<Boolean> = loadingProgress
val loadingFailed: MutableLiveData<Boolean> = MutableLiveData()
val _loadingFailed: LiveData<Boolean> = loadingFailed
var isConnected: Boolean = false
fun fetchmainRepos() {
if (isConnected) {
loadingProgress.value = true
compositeDisposable.add(
mainRepository.getmainRepos().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
run {
saveDataToDb(response)
)
}
},
{ error ->
processResponse(Response(AppConstants.Status.SUCCESS, null, error))
}
)
)
} else {
fetchFromLocal()
}
}
private fun saveDataToDb(response: List<mainRepo>) {
mainRepository.insertmainUsers(response)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
Log.d("Status", "Save Success")
}
override fun onError(e: Throwable) {
Log.d("Status", "error ${e.localizedMessage}")
}
})
}
}
MainRepository
interface MainRepository {
fun getmainRepos(): Single<List<mainRepo>>
fun getAllLocalRecords(): Single<List<mainRepo>>
fun insertmainUsers(repoList: List<mainRepo>): Completable
}
MainRepositoryImpl
class mainRepositoryImpl #Inject constructor(
val apiService: GitHubApi,
val mainDao: AppDao
) : MainRepository {
override fun getAllLocalRecords(): Single<List<mainRepo>> = mainDao.getAllRepos()
override fun insertmainUsers(repoList: List<mainRepo>) :Completable{
return mainDao.insertAllRepos(repoList)
}
override fun getmainRepos(): Single<List<mainRepo>> {
return apiService.getmainGits()
}
}
I'm quite confused with the implementation of MVVM with LiveData and Rxjava, in my MainViewModel I am calling the interface method and implementing it inside ViewModel, also on the response I'm saving the response to db. However, that is a private method, which won't be testable in unit testing in a proper way (because it's private). What is the best practice to call other methods on the completion of one method or i have to implement all the methods inside the implementation class which uses the interface.
Your ViewModel should not care how you are getting the data if you are trying to follow the clean architecture pattern. The logic for fetching the data from local or remote sources should be in the repository in the worst case where you can also save the response. In that case, since you have a contact for the methods, you can easily test them. Ideally, you could break it down even more - adding Usecases/Interactors.
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)