I am trying to pass data from Activity A to Activity B but without using Intent putExtra nor using SharePreferences, I'm using a MVVM pattern in kotlin, so right now I'm using an object declaration like this
object SharedData{ var myMovies: ArrayList<Movie>? = null }
So later on in Activity A i'm assigning a value like this
val movieList = ArrayList<Movie>()
movieList.add(Movie("The Purge"))
SharedData.myMovies = movieList
And then in Activity B i retrieve this value by:
val movieList = ArrayList<Movie>()
SharedData.myMovies.let {
movieList = it
}
But I'm new in kotlin and now I know this is not the correct approach. because the singleton object allocates memory and it never gets collected by the GC. So now I'm stucked here.
Any guidance or help would be appreciated
So, if you're using MVVM pattern it's very straight forward. Use the basic ViewModel implementation with Android Architecture Components. See more of this in https://developer.android.com/topic/libraries/architecture/
class MyActivity : AppCompatActivity() {
private lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_layout)
myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
myViewModel.myObject.observe(this, Observer { mySharedObject ->
//TODO whatever you want to do with your data goes here
Log.i("SomeTag", mySharedObject.anyData)
})
}
}
class MyCoachFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.let {
myViewModel = ViewModelProviders.of(it).get(MyViewModel::class.java)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val myObject = MyObject() // what ever object you need to share between Activities and Fragments, it could be a data class or any object
myViewModel.myObject.postValue(myObject)
}
}
class MyViewModel : ViewModel() {
var myObject = MutableLiveData<MyObject>()
}
My suggestion would be - If you want to share the data just between two activities, you should use intent and send the content as parcelable Object( parcelableArray for your Movielist scenario ) to the next activity. This would be clean implementation.
But I'm new in kotlin and now I know this is not the correct approach.
It is not wrong approach either, can be used depends on your use case. If it meets all the below scenarios, you can go for static variable approach. But Static object will be cleared when the app is killed (either by user or by system)
1.If the stored data size is less.
2.Data does not need to be persisted on App kill and relaunch.
3.Data is shared across many activities.
the singleton object allocates memory and it never gets collected by
the GC
Yes. It's true. Static variables are not eligible for garbage collection. But as long as the memory print is very less, it's okay to use static variable provided it meets above mentioned scenarios.
Related
So I was working on some sample MVVM project using Dagger. I have a viewmodel factory that goes like this:
class DaggerViewModelFactory #Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModelsMap[modelClass] ?:
viewModelsMap.asIterable().firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
A viewmodel factory module
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}
I got a ViewModelModule:
#Module
abstract class MyViewModelModule {
#Binds
#IntoMap
#ViewModelKey(TakePicturesViewModel::class)
abstract fun bindTakePictureViewModel(takePicturesViewModel: TakePicturesViewModel): ViewModel
}
A component that goes like this:
#PerActivity
#Subcomponent(modules = [ActivityModule::class, ViewModelFactoryModule::class, MyViewModelModule::class])
interface ActivityComponent {
fun inject(mainActivity: MainActivity)
}
An a viewmodel that goes like this:
class TakePicturesViewModel #Inject constructor(app: Application): AndroidViewModel(app) {...
So I can either inject my viewmodel in my activity using a view model factory like this:
#Inject
lateinit var viewModelFactory: DaggerViewModelFactory
private lateinit var takePicturesViewModel: TakePicturesViewModel
.
.
.
takePicturesViewModel = ViewModelProviders.of(this, viewModelFactory).get(TakePicturesViewModel::class.java)
Or with not viewmodel factory at all, like this:
#Inject
lateinit var takePicturesViewModel: TakePicturesViewModel
Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?
Thanks in advance for any advice.
Greetings
Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?
Both ways work differently. Try rotating your screen with stored data in your ViewModel and you'll see.
Dagger can create the ViewModel, which is what you make use of in that generic ViewModelFactory. Those view models should be unscoped, thus you'll be creating a new ViewModel every single time. The Android support library will cache that ViewModel and reuse it after rotation so that you can keep your data—the factory method gets called once and there will only ever be one ViewModel created (per lifecycle). You keep your data and everything behaves as expected.
If on the other hand you use Dagger to inject your ViewModel directly none of the above will apply. Like any other dependency, a new ViewModel will be injected on creation, leading to a ViewModel being created every single time it is used—you'll not only use the data stored in it, you won't be able to share state with fragments either.
Of course you could apply a scope to the ViewModel, but that scope should be longer lived than the Activity instance (to keep state between rotations), but no longer lived than the screen is visible. So you can neither scope it to the activity nor to the application lifecycle. You can get it to work by introducing a new scope in-between, but at this point you'd be reinventing the ViewModel library.
tl;dr Inject and use the factory or you'll get a confusing/wrong ViewModel implementation.
I have a working linked list data structure for my Swift project but I do not know where to create the object so it can be manipulated. The object needs to persist throughout the app and accessible from different views controllers.
Someone please point me in the right direction. Still looking for help.
Can't if I can create the object I'll be able to connect the data from taps and swipes.
If you want to use only one, unique linked list in you app, you can use a singleton.
class LinkedList {
static let shared = LinkedList()
private init(){}
private var head: Node?
private var tail: Node?
func append(node: Node) {
// your implementation
}
func getHead() -> Node? {
return head
}
// some other methods
}
The private init makes sure that the only possible instance of the LinkedList that you can get is the static one.
You access it like this:
var myUniqueLinkedList = LinkedList.shared
If you want to read more about singletons check this out:
https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift
Singletons are a rather controversial topic: What is so bad about singletons?
I am a beginner in Kotlin and trying to implement MVVM design pattern on android development. I have to implement a Recyclerview in a fragment.
How we can set adapter with value to a recyclerview from the viewmodel class since the api call is observed within the viewmodel.
My fragment class is look like as below
class NotesFragment : Fragment() {
lateinit var binding:FragmentNotesBinding
lateinit var viewModel:NoteListViewModel
companion object {
fun newInstance(param1: String): NotesFragment {
val fragment = NotesFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.fragment_notes,container,false)
viewModel = NoteListViewModel(binding)
return binding.root
}
is it good practice that we passing the binding object to our viewmodel class and updating the viewModel object again from ViewModel class as below
private fun onSuccess(success: NoteResponse?) {
dataVisibility.value=View.VISIBLE
success.let {
noteAdapter= noteAdapter(documentResponse?.result,mContext)
binding.viewModel=this
}
}
The core about MVVM is seperation of concerns. ViewModel should not hold any reference to the View(Activity/Fragment). LikeWise your Data/Repository layer should not hold ViewModel reference.
So to achieve data flow you can use either Reactive Observables(Rx)/ LiveData from android architecture components to pass back the data.
1) Create MutableLiveData in your Viewmodel.
2) Set the MutableLiveData with api response model.
3) Observe the MutableLiveData in your Fragment for the response data.
4) Use the data to set your adapter inside your fragment.
Please check ViewModel - Developer document to understand better.
In my Kotlin desktop application using TornadoFX, I have created an AudioCard layout (subclass of VBox) which has a few labels and basic audio player controls. This AudioCard has an AudioCardViewModel which handles events from the UI and an AudioCardModel which holds information like the title, subtitle, audio file path, etc. A simplified version is shown below.
data class AudioCardModel(
var title: String,
var audioFile: File
)
class AudioCardViewModel(title: String, audioFile: File) {
val model = AudioCardModel(title, audioFile)
var titleProperty = SimpleStringProperty(model.title)
fun playButtonPressed() {
// play the audio file from the model
}
}
class AudioCard(title: String, audioFile: File) : VBox() {
val viewModel = AudioCardViewModel(title, audioFile)
init {
// create the UI
label(title) {
bind(viewModel.titleProperty)
}
button("Play") {
viewModel.playButtonPressed()
}
}
}
Up until this point, I have tried to keep the code as general as possible, allowing myself or others to reuse this UI component in future applications that need to play audio. However, for my current application, it makes the most sense to have a more specialized version of this UI component that initializes itself directly from my data model class and can extend some of the actions. I've tried something like this (the required fields and classes from the previous code block were switched to open):
data class CustomAudioCardModel(
var customData: CustomData
)
class CustomAudioCardViewModel(customData: CustomData)
: AudioCardViewModel(customData.name, customData.file) {
val model = CustomAudioCardModel(customData)
override fun playButtonPressed() {
super.playButtonPressed()
// do secondary things only needed by CustomAudioCardViewModel
}
}
class CustomAudioCard(customData: CustomData): AudioCard(customData.name, customData.file) {
override val viewModel = CustomAudioCardViewModel(customData)
}
Unfortunately, this isn't so straightforward. By overriding viewModel in CustomAudioCard, the viewModel property ceases to be final, causing a NullPointerException when the init function of the AudioCard superclass tries to use the view model to set up the title label before the child class has initialized the view model.
I suspect there might be a way out of this by defining an AudioCardViewModel interface and/or using Kotlin's ability to delegate with the by keyword, but I'm under the impression that defining the interface (like in MVP) shouldn't be necessary for MVVM.
To summarize: What is the correct way to extend an existing MVVM control, specifically in the context of the Kotlin TornadoFX library?
Here is the solution I came across from Paul Stovell. Instead of creating the view model within the view (Option 1 in Stovell's article), I switched to injecting the view model into the view (Option 2). I also refactored for better MVVM adherence with help from the TornadoFX documentation and this answer regarding where business logic should go. My AudioCard code now looks like this:
open class AudioCardModel(title: String, audioFile: File) {
var title: String by property(title)
val titleProperty = getProperty(AudioCardModel::title)
var audioFile: File by property(audioFile)
val audioFileProperty = getProperty(AudioCardModel::audioFile)
open fun play() {
// play the audio file
}
}
open class AudioCardViewModel(private val model: AudioCardModel) {
var titleProperty = bind { model.titleProperty }
fun playButtonPressed() {
model.play()
}
}
open class AudioCard(private val viewModel: AudioCardViewModel) : VBox() {
init {
// create the UI
label(viewModel.titleProperty.get()) {
bind(viewModel.titleProperty)
}
button("Play") {
viewModel.playButtonPressed()
}
}
}
The extension view now looks like:
class CustomAudioCardModel(
var customData: CustomData
) : AudioCardModel(customData.name, customData.file) {
var didPlay by property(false)
val didPlayProperty = getProperty(CustomAudioCardModel::didPlay)
override fun play() {
super.play()
// do extra business logic
didPlay = true
}
}
class CustomAudioCardViewModel(
private val model: CustomAudioCardModel
) : AudioCardViewModel(model) {
val didPlayProperty = bind { model.didPlayProperty }
}
class CustomAudioCard(
private val viewModel: CustomAudioCardViewModel
) : AudioCard(customViewModel) {
init {
model.didPlayProperty.onChange { newValue ->
// change UI when audio has been played
}
}
}
I see a few ways to clean this up, especially regarding the models, but this option seems to work well in my scenario.
Whenever I call ShowViewModel, somehow a ViewModel and a View of the requested types are retrieved and are bound together for display on the screen. When are new instances of the ViewModel and View created versus looked up and retrieved from a cache somewhere? If new instances are always created and I choose to make my own cache to prevent multiple instances, then how do I show my cached ViewModel instance?
When are new instances of the ViewModel and View created versus looked up and retrieved from a cache somewhere?
Never - for new navigations the default behaviour is always to create new instances.
if... how do I show my cached ViewModel instance?
If for whatever reason you want to override the ViewModel location/creation, then there's information available about overriding the DefaultViewModelLocator in your App.cs in:
MVVMCross Passing values to ViewModel that has 2 constructors
http://slodge.blogspot.co.uk/2013/01/navigating-between-viewmodels-by-more.html
Put simply, implement your code:
public class MyViewModelLocator
: MvxDefaultViewModelLocator
{
public override bool TryLoad(Type viewModelType, IDictionary<string, string> parameterValueLookup,
out IMvxViewModel model)
{
// your implementation
}
}
then return it in App.cs:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
{
return new MyViewModelLocator();
}
Note that older posts like How to replace MvxDefaultViewModelLocator in MVVMCross application are still conceptually compatible - but the details in those older posts are now out of date.
In MvvmCross v3.5 you can use this Class:
public class CacheableViewModelLocator : MvxDefaultViewModelLocator{
public override IMvxViewModel Load(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState)
{
if (viewModelType.GetInterfaces().Any(x=>x == typeof(ICacheableViewModel)))
{
var cache = Mvx.Resolve<IMvxMultipleViewModelCache>();
var cachedViewModel = cache.GetAndClear(viewModelType);
if (cachedViewModel == null)
cachedViewModel = base.Load(viewModelType, parameterValues, savedState);
cache.Cache(cachedViewModel);
return cachedViewModel;
}
return base.Load(viewModelType, parameterValues, savedState);
}}
in your App Code override this method:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator(){
return new CacheableViewModelLocator();}
Create an interface "ICacheableViewModel" and implement it on your ViewModel.
Now you can share the same ViewModel instance with multiple Views.