Hi I am reading this example of LiveData and Observer https://code.tutsplus.com/tutorials/introduction-to-android-architecture--cms-28749
MainActivityViewModel.kt
class MainActivityViewModel : ViewModel() {
private var notes: MutableLiveData<List<String>>? = null
fun getNotes(): LiveData<List<String>> {
if (notes == null) {
notes = MutableLiveData<List<String>>()
loadNotes()
}
return notes!!
}
private fun loadNotes() {
// do async operation to fetch notes
}
}
MainActivity.kt
class MainActivity : LifecycleActivity(), AnkoLogger {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this)
.get(MainActivityViewModel::class.java)
viewModel.getNotes().observe(
this, Observer {
notes -> info("notes: $notes")
}
)
}
}
How LiveData is sending data to MainActivity if there is any changes in notes (new or delete). I see activity is calling viewModel.getNotes() which may not get called once onCreate method finish.
LiveData isn't sending anything to MainActivity, it's "sending" to the Observer passed to the observe method. This Observer has a reference to MainActivity where it was created and can call its methods.
Related
In my application I want use MVI for application architecture and I should use Room database.
I have one activity and one fragment!
In fragment I receive data from user and save into database and in activity show this data into recyclerview.
I write below codes and my data successfully save into database!
But for show it into activity, I should exit from application and enter to show data list!
I want without exit from application, update automatically this list.
Dao codes :
#Query("SELECT * FROM my_table")
fun getAllData(): MutableList<Entity>
Repository codes:
class MyRepository #Inject constructor(private val dao: DataDao) {
fun allData() = dao.getAllData()
}
ViewModel codes:
#HiltViewModel
class MyViewModel #Inject constructor(private val repository: MyRepository) : ViewModel() {
val mainIntent = Channel<MainIntent>()
private val _state = MutableStateFlow<MainState>(MainState.Idle)
val state : StateFlow<MainState> get() = _state
init {
handleIntent()
}
private fun handleIntent() {
viewModelScope.launch {
mainIntent.consumeAsFlow().collect{
when(it){
is MainIntent.LoadAllData-> fetchingAllDataList()
}
}
}
}
private fun fetchingAllDataList() {
viewModelScope.launch {
_state.value = MainState.LoadData(repository.allData())
}
}
}
Activity codes :
lifecycleScope.launch {
//Send
viewModel.mainIntent.send(MainIntent.LoadAllData)
//Get
viewModel.state.collect { state ->
when (state) {
is MainState.Idle -> {}
is MainState.LoadData -> {
dataAdapter.setData(state.list)
fataList.apply {
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = noteAdapter
}
}
}
}
}
How can I fix this problem?
Several methods below can solve this problem.
Use EventBus. Send an EventBus message after saving data to database in fragment and handle this message in MyViewModel or your Activity to reload data
In your 'dao' interface, change function return type to LiveData: fun getAllData(): LiveData<MutableList<Entity>>. When related data changed in database, Room database automaticly notify changes to Observers. Check this
Use Broadcast like using EventBus
If fragment is contained in the Activity which requires the notification when data changed, use SharedViewModel to notify activity.
class MyFragment: BottomDialogSheetFragment {
var entityChangeListener: IEntityChangeListener? = null
...
// after saving data to database
entityChangeListener?.onChanged()
}
class MyActivity {
fun showDialog() {
val fragment = MyFragment()
fragment.entityChangeListener = object : IEntityChangeListener {
override fun onChanged() {
// change [fetchAllDataList] function to public
myViewModel.fetchAllDataList()
}
}
}
}
interface IEntityChangeListener {
fun onChanged()
}
// using SharedViewModel
class MyFragment: BottomDialogSheetFragment {
var entityViewModel by sharedViewModel<EntityViewModel>
...
// saving entity data
entityViewModel.saveData(entities)
}
class MyActivity {
// shared view model for entity database business
val entityViewModel by viewModels<EntityViewModel>
// viewmodel for other business logic
val viewModel by viewModels<MyViewModel>
}
class EntityViewModel: ViewModel(){
...
private val _state = MutableStateFlow<MainState>(MainState.Idle)
val state : StateFlow<MainState> get() = _state
fun fetchingAllDataList() {
viewModelScope.launch(Dispatchers.IO) {
_state.value = MainState.LoadData(repository.allData())
}
}
fun saveData(entities: List<Entity>) {
dao.save(entities)
fetchingAllDataList()
}
}
If anyone can see something wrong with my implementation I would greatly appreciate knowing what it is. The activity does not seem to be observing updates to the MutableLiveData I am making.
ViewModel
class MyViewModel : ViewModel() {
val myLiveData: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
fun updateData(newValue: String) {
myLiveData.postValue(newValue)
}
}
Activity
class MyActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel.myLiveData.observe(this, Observer {
myTextView.text = it
})
}}
I know updateData works because if I call it form the activity with an arbitrary string it updates the UI.
In your .observe, you are missing the new value which is being returned whenever your live data updates.
myViewModel.myLiveData.observe(this, Observer {
myTextView.text = it
})
It should look like this, where "it" can be named anything:
myViewModel.myLiveData.observe(this, Observer { it ->
myTextView.text = it
})
I want to load data from an API when activity is started. Currently, I call a view model's method from the activity to load data and it's working fine, but I don't know if it's the best way to do it:
Activity
override fun onCreate(savedInstanceState: Bundle?) {
//initialize stuff...
viewModel.myData.observe(this) {
//do things with the data
}
lifeCycleScope.launch { viewModel.loadData() }
}
ViewModel
class MyViewModel : ViewModel() {
val myData = MutableLiveData<MyData>()
suspend fun loadData() = withContext(Dispatchers.IO) {
val data = api.getData()
withContext(Dispatchers.Main) {
myData.value = data
}
}
}
I have seen some examples using lazy initialization, but I don't know how to implement it with coroutines. I have tried this:
Activity
override fun onCreate(savedInstanceState: Bundle?) {
//initialize stuff...
viewModel.myData().observe(this) {
//do things with the data
}
}
ViewModel
private val myData : MutableLiveData<MyData> by lazy {
MutableLiveData<MyData>().also {
viewModelScope.launch {
loadData()
}
}
}
fun myData() = myData
suspend fun loadData() = // same as above
But data is not fetched and nothing is displayed.
If you've added dependency livedata-ktx then you can use livedata builder to also have API call in same block and emit. Checkout how you can do it:
class MyViewModel : ViewModel() {
val myData: LiveData<MyData> = liveData {
val data = api.getData() // suspended call
emit(data) // emit data once available
}
}
I have a ViewModel that inherits from a base class and I would like to have a corresponding Activity also inherit from a base class. The activity would call the same method of the derived ViewModel each time. So it would be something like this:
BaseViewModel:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application) {
protected val context = getApplication<Application>().applicationContext
protected var speechManager: SpeechRecognizerManager? = null
var _actionToTake : MutableLiveData<AnalyseVoiceResults.Actions> = MutableLiveData()
var actionToTake : LiveData<AnalyseVoiceResults.Actions> = _actionToTake
open fun stopListening() {
if (speechManager != null) {
speechManager?.destroy()
speechManager = null
}
open fun startListening() {
val isListening = speechManager?.ismIsListening() ?: false
if (speechManager == null) {
SetSpeechListener()
} else if (!isListening) {
speechManager?.destroy()
SetSpeechListener()
}
}
}
BaseActivity
class BaseActivity : AppCompatActivity() {
private lateinit var baseViewModel: BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun goback() {
super.onBackPressed()
baseViewModel.stopListening()
finish()
}
fun startListening() {
baseViewModel.startListening()
}
override fun onDestroy() {
super.onDestroy()
baseViewModel.stopListening()
}
}
Derived Activity:
class DerivedActivity : BaseActivity() {
private val nextActivityViewModel: NextActivityViewModel by inject()
///^^inherits from BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*** pass reference ***/
baseViewModel = nexActivityViewModel
nextActivityViewModel.actionToTake.observe(this, object : Observer<AnalyseVoiceResults.Actions?> {
override fun onChanged(t: AnalyseVoiceResults.Actions?) {
if (t?.equals(AnalyseVoiceResults.Actions.GO_BACK) ?: false) {
goback()
}
}
})
startListening()
}
}
Will this cause memory leaks to have two instances of a view model for this activity? Is there a better way to do this? I don't want to keep repeating the same code for all my activities. (I would also have the same question if I was doing this with one base fragment).
make this var baseViewModel: BaseViewModel an abstract variable where all the children class must override it. So, when you call the startListening and stopListening, these methods will be called from children implementation.
Edit:
Make the BaseActivity an abstract class and the baseViewModel as an abstract variable
abstract class BaseActivity : AppCompatActivity() {
private abstract var baseViewModel: BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun goback() {
super.onBackPressed()
baseViewModel.stopListening()
finish()
}
fun startListening() {
baseViewModel.startListening()
}
override fun onDestroy() {
super.onDestroy()
baseViewModel.stopListening()
}
}
So, your DerivedActivity must override the baseViewModel, and every call on father's class will trigger the child
class DerivedActivity : BaseActivity() {
override val baseViewModel: NextActivityViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Will this cause memory leaks to have two instances of a view model for
this activity?
No, there are no memory leaks with this approach. Nor do you have 2 instances of the ViewModel for the same activity. It's a single instance of ViewModel which is being referenced with different variables in BaseActivity and BaseViewModel.
Is there a better way to do this?
The first issue I see is that you have Android specific code in the ViewModels, which is not considered a good practice. You should move the speech manager code to the base activity itself, and ViewModel should only hold the "state" data that you want to retain on orientation changes. This will ensure all the Speech Management methods (create, resume, destroy) will be in the base activity. Concrete activity will only have observers if the state changes.
If you are following any architecture pattern (like MVP), once you move the Speech Manager code out to activity, it would become obvious to move this further out to the Presenter.
EDIT: I have not used the MVVM pattern in production, but this is a light variant of what you may want:
The basic idea is to move Speech management code in a lifecycle-aware component. All UI code in view/activity and business logic / non-android state in viewmodel. I don't see a point in having base activity or viewmodel based on the requirements you have shared so far.
/**
* All the speech related code is encapsulated here, so any new activity/fragment can use it by registering it's lifecycle
*/
class SpeechManager(private val context: Context): LifecycleObserver {
val TAG = "SpeechManager"
private var speechRecognizer: SpeechRecognizer? = null
fun registerWithLifecycle(lifecycle: Lifecycle) {
Log.e(TAG, "registerWithLifecycle")
lifecycle.addObserver(this)
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
Log.e(TAG, "start")
speechRecognizer = (speechRecognizer ?: SpeechRecognizer.createSpeechRecognizer(context)).apply {
// setRecognitionListener(object : RecognitionListener {
// //implement methods
// })
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
Log.e(TAG, "stop")
speechRecognizer?.run {
stopListening()
destroy()
}
}
}
ViewModel:
class SpeechViewModel: ViewModel() {
val TAG = "SpeechViewModel"
//List all your "data/state" that needs to be restores across activity restarts
private val actions: MutableLiveData<Actions> = MutableLiveData<Actions>().apply { value = Actions.ActionA }
//Public API for getting observables and all use-cases
fun getActions() = actions
fun doActionA(){
//validations, biz logic
Log.e(TAG, "doActionA")
actions.value = Actions.ActionA
}
fun doActionB(){
Log.e(TAG, "doActionB")
actions.value = Actions.ActionB
}
}
sealed class Actions{
object ActionA: Actions()
object ActionB: Actions()
}
Activity/View:
class SpeechActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_speech)
setSupportActionBar(toolbar)
initSpeechManager()
}
private lateinit var speechManager: SpeechManager
private lateinit var speechViewModel: SpeechViewModel
/**
* Register lifecycle aware components and start observing state changes from ViewModel.
* All UI related code should ideally be here (or your view equivalent in MVVM)
*/
private fun initSpeechManager() {
speechManager = SpeechManager(this).apply {registerWithLifecycle(lifecycle)}
speechViewModel = ViewModelProviders.of(this).get(SpeechViewModel::class.java).apply {
getActions().observe(this#SpeechActivity, Observer<Actions>{
when(it){
is Actions.ActionA -> {
Log.e(TAG, "Perform ActionA")
speechManager.start()
}
is Actions.ActionB -> {
Log.e(TAG, "Perform ActionB")
speechManager.stop()
super.onBackPressed()
}
}
})
}
}
}
FooActivity.kt:
class FooActivity : AppCompatActivity(), LifecycleRegistryOwner {
override fun getLifecycle(): LifecycleRegistry {
return LifecycleRegistry(this)
}
..
// <-- here mViewModel is null
mViewModel.getBar().observe(this, Observer<List<String>> {
override fun onChanged(bar: List<String>) {
// Never triggered
}
})
mViewModel.init()
// <-- here mViewModel has changed
}
The mViewModel is confirmed to change. However the observer's onChanged is never called.
Question: why doesn't it work?
Edit: FooViewModel.kt:
class FooViewModel(application: Application) : AndroidViewModel(application) {
val baz: BazPagerAdapter? = null
..
fun init(fm: FragmentManager) {
mBar = listOf("1", "2", "3")
}
..
fun getBar(): List<String> = mBar
..
fun setBaz(pager: ViewPager, periods: List<BazFragment>) {
pager.adapter = BazPagerAdapter(mFragmentManager!!, periods)
}
}
Edit2:
For got to mentiond, getBar already returns LiveData
fun getBar(): LiveData<List<String>> = mBar
And the onChange still wouldn't trigger.
Edit3:
class FooViewModel(application: Application) : AndroidViewModel(application) {
private var mBar: MutableLiveData<List<String>>? = null
..
fun init(fragmentManager: FragmentManager) {
..
if (mBar == null) {
mBar = MutableLiveData<List<String>>()
}
mBar?.value = periods
}
..
fun getBar(): LiveData<List<String>>? = mBar
There is no observe method for type List.
The ViewModel has nothing to do with observing either, it is there mainly to have state that persists through configuration changes.
For observable data you want (Mutable)LiveData objects. These are lifecycle aware and manage observers for their data.
Please see the code examples here:
public class MyViewModel : ViewModel() {
private val mBar = MutableLiveData<List<String>>()
fun getBar(): LiveData<List<String>> = mBar
fun init() {
mBar.setValue(listOf("1", "2", "3"))
}
}
Ok, so I have managed to find the answer.
If you are going to extend AppCompatActivity and you want to use LiveData, you will have to implement the LifecycleRegistryOwner interface and its only method - getLifecycle.
The problem was that:
override fun getLifecycle(): LifecycleRegistry {
return LifecycleRegistry(this)
}
Had to be:
val mLifecycleRegistry = LifecycleRegistry(this)
..
override fun getLifecycle(): LifecycleRegistry {
return mLifecycleRegistry
}
So the problem is when you call init method, it reassigns new instance of LiveData into mBar. And you have assigned observer to previous instance of mBar because you are calling init method after :
mViewModel.getBar().observe(this, Observer<List<String>> {
override fun onChanged(bar: List<String>) {
// Never triggered
}
})
To solve the problem just initialise mBar with MutableLiveData and then change its value (Do not re-assign mBar with another instance).
Check following code:
class FooViewModel(application: Application) : AndroidViewModel(application) {
private var mBar: MutableLiveData<List<String>> = MutableLiveData()
fun init(fragmentManager: FragmentManager) {
mBar.value = periods // Changing value only, not new instance
}
fun getBar(): LiveData<List<String>> = mBar
}