Android ViewModel is leaking - android

LeakCanary is telling me that one of my ViewModels is leaking but after playing around for 2 days I can't get the leak to go away.
Here is why LeakCanary shows
Here is the Fragment getting the ViewModel
viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java).apply {
getStrains(arguments?.getString(BREEDER_ID_KEY, "")!!)
}
Here is the ViewModel
class ViewBreederViewModel(application: Application) : AndroidViewModel(application) {
private val breederRepository = BreederRepository(application)
val strainList = MutableLiveData<List<MinimalStrain>>()
fun getStrains(breederId: String) {
viewModelScope.launch {
breederRepository.getMinimalStrains(breederId).observeForever {
strainList.value = it
}
}
}
}
Here is the BreederRepository:
class BreederRepository(context: Context) {
private val dao: BreederDao
private val breederApi = RetrofitClientInstance.getInstance(context).breederAndStrainIdsApi
init {
val database: Db = Db.getInstance(
context
)!!
dao = database.breederDao()
}
suspend fun getMinimalStrains(breederId: String): LiveData<List<MinimalStrain>> =
withContext(Dispatchers.IO) {
dao.getMinimalStrains(breederId)
}
}
Here is the Db class
#Database(
entities = [Breeder::class, Strain::class],
version = 1,
exportSchema = true)
#TypeConverters(RoomDateConverter::class)
abstract class Db : RoomDatabase() {
abstract fun breederDao(): BreederDao
companion object {
private var instance: Db? = null
#JvmStatic
fun getInstance(context: Context): Db? {
if (instance == null) {
synchronized(Db::class) {
instance = Room.databaseBuilder(
context.applicationContext,
Db::class.java, "seedfinder_db"
)
.build()
}
}
return instance
}
}
}

You're using observeForever, which, as the name suggest, will keep observing forever, even after your ViewModel is cleared. Room does not require using a suspend method for DAO methods that return a LiveData and that is never the right approach in any case - LiveData is already asynchronous.
Instead, you should be transforming your LiveData, using your breederId as the input to your strainList LiveData:
class ViewBreederViewModel(application: Application) : AndroidViewModel(application) {
private val breederRepository = BreederRepository(application)
private val currentBreederId = MutableLiveData<String>()
// Here we use the switchMap method from the lifecycle-livedata-ktx artifact
val strainList: LiveData<String> = currentBreederId.switchMap {
breederId -> breederRepository.getMinimalStrains(breederId)
}
private fun setBreederId(breederId: String) {
currentBreederId.value = breederId
}
}
Where your getMinimalStrains becomes:
fun getMinimalStrains(breederId: String): LiveData<List<MinimalStrain>> =
dao.getMinimalStrains(breederId)
And you use it by setting your breederId in your UI and observing your strainList as before:
viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java).apply {
setBreederId(arguments?.getString(BREEDER_ID_KEY, "")!!)
}
viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
// use your updated list
}
If you're using Saved State module for ViewModels (which is the default if you're using the latest stable Fragments / Activity libraries), then you can use SavedStateHandle, which is automatically populated from your Fragment's arguments and skip the setBreederId() entirely:
class ViewBreederViewModel(
application: Application,
savedStateHandle: SavedStateHandle
) : AndroidViewModel(application) {
private val breederRepository = BreederRepository(application)
// Here we use the switchMap method from the lifecycle-livedata-ktx artifact
val strainList: LiveData<String> = savedStateHandle
.getLiveData(BREEDER_ID_KEY) // Automatically populated from arguments
.switchMap {
breederId -> breederRepository.getMinimalStrains(breederId)
}
}
Which means your code can simply become:
viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java)
viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
// use your updated list
}
And if you use the fragment-ktx artifact, you can simplify this further to:
// Move this to where you declare viewModel
val viewModel: ViewBreederViewModel by viewModels()
viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
// use your updated list
}

Related

How do I initialize one LiveData object to another?

The following class has a method than can load a list of users and store it in a LiveData wrapper:
class UserLoader {
...
val loadedUsersLiveData: MutableLiveData<List<User>> = MutableLiveData()
fun loadUsers() {
...
val userRequest: Call<UserResponse> = userApi.loadUsers()
userRequest.enqueue(object : Callback<UserResponse> {
...
override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
...
loadedUsersLiveData.value = ... // assigns the list of users returned
}
...
}
}
How would I initialize the LiveData variable in my class below to the value of the one that's fetched in the class above?
class UserTableViewModel : ViewModel() {
// TODO: initialize usersLiveData to UserLoader's loadedUsersLiveData
val usersLiveData: LiveData<List<User>> // ??
fun loadUsers() {
UserLoader().loadUsers()
}
}
class UserTableViewModel : ViewModel() {
private val userLoader = UserLoader()
// TODO: initialize usersLiveData to UserLoader's loadedUsersLiveData
val usersLiveData: LiveData<List<User>> = userLoader.loadedUsersLiveData
fun loadUsers() {
userLoader.loadUsers()
}
}
Don't directly expose mutable properties always expose immutable properties for reading the values.
on side note: you can make class UserLoader() to object if you want to safe to call it from everywhere or don't have constructor values.
LiveData is an interface but what you can do is initialize it through MutableLiveData or another LiveData of same type which in your case from UserLoader.
You can do:
class UserTableViewModel : ViewModel() {
private val _userLiveData = MutableLiveData<List<User>>()
val usersLiveData: LiveData<List<User>> get() = _userLiveData // observe it in the view..
// don't forget to call this function...
fun updateUserList() {
_userLiveData.value = UserLoader().loadedUsersLiveData
}
fun loadUsers() {
UserLoader().loadUsers()
}
}
or
class UserTableViewModel : ViewModel() {
//observe it in view..
val usersLiveData: LiveData<List<User>> = UserLoader().loadedUsersLiveData
}
fun loadUsers() {
UserLoader().loadUsers()
}

Kotlin coroutines. Kotlin Flow and shared preferences. awaitClose is never called

I'd love to observe changes of a shared preference. Here is how I Use Kotlin Flow to do it:
Data source.
interface DataSource {
fun bestTime(): Flow<Long>
fun setBestTime(time: Long)
}
class LocalDataSource #Inject constructor(
#ActivityContext context: Context
) : DataSource {
private val preferences = context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE)
#ExperimentalCoroutinesApi
override fun bestTime() = callbackFlow {
trySendBlocking(preferences, PREF_KEY_BEST_TIME)
val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == PREF_KEY_BEST_TIME) {
trySendBlocking(sharedPreferences, key)
}
}
preferences.registerOnSharedPreferenceChangeListener(listener)
awaitClose { // NEVER CALLED
preferences.unregisterOnSharedPreferenceChangeListener(listener)
}
}
#ExperimentalCoroutinesApi
private fun ProducerScope<Long>.trySendBlocking(
sharedPreferences: SharedPreferences,
key: String?
) {
trySendBlocking(sharedPreferences.getLong(key, 0L))
.onSuccess { }
.onFailure {
Log.e(TAG, "", it)
}
}
override fun setBestTime(time: Long) = preferences.edit {
putLong(PREF_KEY_BEST_TIME, time)
}
companion object {
private const val TAG = "LocalDataSource"
private const val PREFS_FILE_NAME = "PREFS_FILE_NAME"
private const val PREF_KEY_BEST_TIME = "PREF_KEY_BEST_TIME"
}
}
Repository
interface Repository {
fun observeBestTime(): Flow<Long>
fun setBestTime(bestTime: Long)
}
class RepositoryImpl #Inject constructor(
private val dataSource: DataSource
) : Repository {
override fun observeBestTime() = dataSource.bestTime()
override fun setBestTime(bestTime: Long) = dataSource.setBestTime(bestTime)
}
ViewModel
class BestTimeViewModel #Inject constructor(
private val repository: Repository
) : ViewModel() {
// Backing property to avoid state updates from other classes
private val _uiState = MutableStateFlow(0L)
val uiState: StateFlow<Long> = _uiState
init {
viewModelScope.launch {
repository.observeBestTime()
.onCompletion { // CALLED WHEN THE SCREEN IS ROTATED OR HOME BUTTON PRESSED
Log.d("myTag", "viewModelScope onCompletion")
}
.collect { bestTime ->
_uiState.value = bestTime
}
}
}
fun setBestTime(time: Long) = repository.setBestTime(time)
}
Fragment.
#AndroidEntryPoint
class MetaDataFragment : Fragment(R.layout.fragment_meta_data) {
#Inject
lateinit var timeFormatter: TimeFormatter
#Inject
lateinit var bestTimeViewModel: BestTimeViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bestTimeView = view.findViewById<TextView>(R.id.best_time_value)
// Create a new coroutine in the lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
bestTimeViewModel.uiState
.map { millis ->
timeFormatter.format(millis)
}
.onCompletion { // CALLED WHEN THE SCREEN IS ROTATED OR HOME BUTTON PRESSED
Log.d("MyApp", "onCompletion")
}
.collect {
bestTimeView.text = it
}
}
}
}
}
I've noticed that awaitClose is never called. But this is where my clean-up code is. Please advise. If it's not a good idea to use callbackFlow in the first place, please let me know (as you can see some functions are ExperimentalCoroutinesApi meaning their behaviour can change)
I found a solution that allows me to save a simple dataset such as a preference and observe its changes using Kotlin Flow. It's Preferences DataStore.
This is the code lab and guide I used:
https://developer.android.com/codelabs/android-preferences-datastore#0
https://developer.android.com/topic/libraries/architecture/datastore
and this is my code:
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
data class UserPreferences(val bestTime: Long)
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
interface DataSource {
fun userPreferencesFlow(): Flow<UserPreferences>
suspend fun updateBestTime(newBestTime: Long)
}
class LocalDataSource(
#ApplicationContext private val context: Context,
) : DataSource {
override fun userPreferencesFlow(): Flow<UserPreferences> =
context.dataStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
val bestTime = preferences[PreferencesKeys.BEST_TIME] ?: 0L
UserPreferences(bestTime)
}
override suspend fun updateBestTime(newBestTime: Long) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.BEST_TIME] = newBestTime
}
}
}
private object PreferencesKeys {
val BEST_TIME = longPreferencesKey("BEST_TIME")
}
and the dependency to add to build.gradle:
implementation "androidx.datastore:datastore-preferences:1.0.0"
The problem is, that you are injecting your ViewModel as if it was just a regular class, by using
#Inject
lateinit var bestTimeViewModel: BestTimeViewModel
Because of this, the ViewModel's viewModelScope is never cancelled, and therefor the Flow is collected forever.
Per Documentation, you should use
privat val bestTimeViewModel: BestTimeViewModel by viewModels()
This ensures that the ViewModel's onCleared method, which in turn will cancel the viewModelScope, is called when your Fragment is destroyed.
Also make sure your ViewModel is annotated with #HiltViewModel:
#HiltViewModel
class BestTimeViewModel #Inject constructor(...) : ViewModel()

Is there any advantage to use a viewModelScope or liveDataScope when observing LiveData from a Room database?

Can somebody please explain if there is any advantage to use a viewModelScope or liveDataScope when observing LiveData from a Room database?
My current understanding is, that I have to insert, query, delete or update data to/from a Room database from a thread that is different from the UI thread. Therefore I have been using Kotlin Coroutines with viewModelScope.launch(Dispatcher.IO) { ... } in a MVVM architecture containing a ViewModel and a Repository - similar to the setComplete function in the viewModel code below.
However, in the TasksViewModel class (from this project https://github.com/android/architecture-samples) tasksRepository.observeTask(taskId).map { computeResult(it) } returns a LiveData object and it is called without wrapping a coroutine scope around it.
Do I understand it correct, that I can omit Coroutines to observe LiveData, because LiveData runs on a background thread anyway?
If so, is there any advantage to wrap a viewModelScope or liveDataScope around tasksRepository.observeTask(taskId).map { computeResult(it) } and how should that look like in the following ViewModel?
TasksViewModel.kt
class TaskDetailViewModel(
private val tasksRepository: TasksRepository) : ViewModel() {
private val _taskId = MutableLiveData<String>()
private val _task = _taskId.switchMap { taskId ->
tasksRepository.observeTask(taskId).map { computeResult(it) }
...
fun setCompleted(completed: Boolean) = viewModelScope.launch {
val task = _task.value ?: return#launch
if (completed) {
tasksRepository.completeTask(task)
showSnackbarMessage(R.string.task_marked_complete)
} else {
tasksRepository.activateTask(task)
showSnackbarMessage(R.string.task_marked_active)
}
}
...
private fun computeResult(taskResult: Result<Task>): Task? {
return if (taskResult is Success) {
taskResult.data
} else {
showSnackbarMessage(R.string.loading_tasks_error)
null
}
}
...
}
DefaultTasksRepository.kt
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override fun observeTasks(): LiveData<Result<List<Task>>> {
return tasksLocalDataSource.observeTasks()
}
...
}
LocalTasksRepository.kt
class TasksLocalDataSource internal constructor(
private val tasksDao: TasksDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksDataSource {
...
override fun observeTask(taskId: String): LiveData<Result<Task>> {
return tasksDao.observeTaskById(taskId).map {
Success(it)
}
}
...
}
TasksDao.kt
#Dao
interface TasksDao {
...
#Query("SELECT * FROM Tasks WHERE entryid = :taskId")
fun observeTaskById(taskId: String): LiveData<Task>
...
}
ToDoDatabase.kt
#Database(entities = [Task::class], version = 1, exportSchema = false)
abstract class ToDoDatabase : RoomDatabase() {
abstract fun taskDao(): TasksDao
}
I hope my question is understandable?

How to use MediatorLiveData with an abstract source

I have an abstract class, with a MediatorLiveData object in it. This object has a number of sources, one of which depends on the childs class, and is abstract in the parent class.
Adding the sources in an init block causes a NullPointerException at runtime, because at the time the init block adds the source, it is still abstract (or so I have been led to believe).
Is there a way to use an abstract LiveData as a source for a MediatorLiveData without having to set that source in a child class? I just want to override val and be done with it, since I definitely will forget to call the addSources() function at some time in the future.
(I am aware that this example is not the most useful way to do this exact thing, but I didn't want to add unneccesary complexity)
Example:
abstract class MyClass: ViewModel(){
private val _myMediator = MediatorLiveData<String>()
protected abstract val mySource: LiveData<String>
val myObservable: LiveData<String>
get() = _myMediator
// This will cause a NullPointerException at runtime
init{
_myMediator.addSource(mySource){ _myMediator.value = it }
}
//This should work, but requires this to be called in child class
protected fun addSources(){
_myMediator.addSource(mySource){ _myMediator.value = it }
}
}
class myChild: MyClass(){
override val mySource = Transformations.map(myRepository.someData) { it.toString() }
// This is where init { addSources() } would be called
}
After reading Stachu's anwser, I decided to go with this, which I didn't test butI think should work:
abstract class MyFixedClass: ViewModel(){
private val _myMediator: MediatorLiveData<String> by lazy{
MediatorLiveData<String>().apply{
addSource(mySource){ this.value = it }
}
}
protected abstract val mySource: LiveData<String>
val myObservable: LiveData<String>
get() = _myMediator
}
class MyChild: MyFixedClass(){
override val mySource = Transformations.map(myRepository.someData) { it.toString() }
}
how about using lazy evaluation, e.g. something like this
abstract class MyClass : ViewModel() {
private val _myMediator = MediatorLiveData<String>()
private val _mySource: LiveData<String> by lazy { mySource() }
protected abstract fun mySource(): LiveData<String>
val myObservable: LiveData<String>
get() = _myMediator
init {
_myMediator.addSource(_mySource) { _myMediator.value = it }
}
}
class myChild : MyClass() {
override fun mySource() = Transformations.map(myRepository.someData) { it.toString() }
}

Working with multiple repositories in one ViewModel on kotlin

Tell me, please, how to make it more correct so that the ViewModel supports working with the desired repository, depending on the viewmodel's parameter? Android application should display a list of requests, requests are of different types. I want to use one fragment for request of different types and in one model I want universally work with a repository that will pull out requests of the required type from the database (Room).
I made a common interface for repositories:
interface RequestRepository<T> {
fun getRequests(): LiveData<List<T>>
fun getRequestById(requestId: String): LiveData<T>
suspend fun insertRequests(requests: List<T>)
suspend fun deleteRequest(request: T)
suspend fun deleteAllRequests()
}
This is one of the repositories:
class PaymentRequestRepository private constructor(private val paymentRequestDao: PaymentRequestDao) : RequestRepository<PaymentRequest> {
override fun getRequests() = paymentRequestDao.getRequests()
override fun getRequestById(requestId: String) = paymentRequestDao.getRequestById(requestId)
override suspend fun insertRequests(requests: List<PaymentRequest>) {
paymentRequestDao.deleteAll()
paymentRequestDao.insertAll(requests)
}
override suspend fun deleteRequest(request: PaymentRequest) = paymentRequestDao.delete(request)
override suspend fun deleteAllRequests() = paymentRequestDao.deleteAll()
companion object {
// For Singleton instantiation
#Volatile private var instance: PaymentRequestRepository? = null
fun getInstance(paymentRequestDao: PaymentRequestDao) =
instance ?: synchronized(this) {
instance ?: PaymentRequestRepository(paymentRequestDao).also { instance = it }
}
}
}
How in the ViewModel to work with the necessary repository depending on the type of request?
class RequestListViewModel(application: Application, val requestType: RequestType): AndroidViewModel(application) {
//lateinit var paymentRequestRepository: PaymentRequestRepository
//lateinit var serviceRequestRepository: ServiceRequestRepository
lateinit var requestRepository: RequestRepository<BaseRequestDao<Request>>
...
init {
val database = AgreementsDatabase.getDatabase(application)
when (requestType) {
RequestType.MONEY -> {
val paymentRequestDao = database.paymentRequestsDao()
requestRepository = PaymentRequestRepository.getInstance(paymentRequestDao)
}
RequestType.SERVICE -> {
val serviceRequestDao = database.serviceRequestsDao()
requestRepository = ServiceRequestRepository.getInstance(serviceRequestDao)
}
RequestType.DELIVERY -> {
val deliveryRequestsDao = database.deliveryRequestsDao()
requestRepository = DeliveryRequestRepository.getInstance(deliveryRequestsDao)
}
}
_requests = requestRepository.getRequests()
updateRequests();
}
}
** When creating a repository, I get a type mismatch error: **
requestRepository = PaymentRequestRepository.getInstance(paymentRequestDao)
Tell me how is this done correctly?

Categories

Resources