Kotlin object (singleton) with an Android Application Context [duplicate] - android

This question already has answers here:
Singleton with parameter in Kotlin
(14 answers)
Closed 2 years ago.
The Kotlin reference says that I can create a singleton using the object keyword like so:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
//
}
}
However, I would like to pass an argument to that object. For example an ApplicationContext in an Android project.
Is there a way to do this?

Since objects do not have constructors what I have done the following to inject the values on an initial setup. You can call the function whatever you want and it can be called at any time to modify the value (or reconstruct the singleton based on your needs).
object Singleton {
private var myData: String = ""
fun init(data: String) {
myData = data
}
fun singletonDemo() {
System.out.println("Singleton Data: ${myData}")
}
}

Kotlin has a feature called Operator overloading, letting you pass arguments directly to an object.
object DataProviderManager {
fun registerDataProvider(provider: String) {
//
}
operator fun invoke(context: ApplicationContext): DataProviderManager {
//...
return this
}
}
//...
val myManager: DataProviderManager = DataProviderManager(someContext)

With most of the existing answers it's possible to access the class members without having initialized the singleton first. Here's a thread-safe sample that ensures that a single instance is created before accessing any of its members.
class MySingleton private constructor(private val param: String) {
companion object {
#Volatile
private var INSTANCE: MySingleton? = null
#Synchronized
fun getInstance(param: String): MySingleton = INSTANCE ?: MySingleton(param).also { INSTANCE = it }
}
fun printParam() {
print("Param: $param")
}
}
Usage:
MySingleton.getInstance("something").printParam()

There are also two native Kotlin injection libraries that are quite easy to use, and have other forms of singletons including per thread, key based, etc. Not sure if is in context of your question, but here are links to both:
Injekt (mine, I'm the author): https://github.com/kohesive/injekt
Kodein (similar to Injekt): https://github.com/SalomonBrys/Kodein
Typically in Android people are using a library like this, or Dagger, et al to accomplish parameterizing singletons, scoping them, etc.

I recommend that you use this form to pass arguments in a singleton in Kotlin debit that the object your constructor is deprived and blocked:
object Singleton {
fun instance(context: Context): Singleton {
return this
}
fun SaveData() {}
}
and you call it this way in the activity
Singleton.instance(this).SaveData()

If you looking for a base SingletonHolder class with more than one argument. I had created the SingletonHolder generic class, which supports to create only one instance of the singleton class with one argument, two arguments, and three arguments.
link Github of the base class here
Non-argument (default of Kotlin):
object AppRepository
One argument (from an example code in the above link):
class AppRepository private constructor(private val db: Database) {
companion object : SingleArgSingletonHolder<AppRepository, Database>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db)
Two arguments:
class AppRepository private constructor(private val db: Database, private val apiService: ApiService) {
companion object : PairArgsSingletonHolder<AppRepository, Database, ApiService>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db, apiService)
Three arguments:
class AppRepository private constructor(
private val db: Database,
private val apiService: ApiService,
private val storage : Storage
) {
companion object : TripleArgsSingletonHolder<AppRepository, Database, ApiService, Storage>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db, apiService, storage)
More than 3 arguments:
To implement this case, I suggest creating a config object to pass to the singleton constructor.

Related

Multiple Instances of ViewModel in Hilt

I apologize if this has been asked before. I am trying to create multiple instances of the same type of viewmodel scoped to an activity using dagger-hilt, but even with different custom default args, it is returning the same instance each time.
I need all the viewmodel instances to be activity scoped, not fragment or navgraph scoped because I need all the fragments to subscribe to the updated data that will be received in the activity.
(Using Kotlin)
Activity Code
#AndroidEntryPoint
class Activity : AppCompatActivity() {
private val vm1:MyViewModel by viewModels(extrasProducer = {
val bundle = Bundle().apply {
putString("ViewModelType", "vm1")
}
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundle)
}
}) {
MyViewModel.Factory
}
private val vm2:MyViewModel by viewModels(extrasProducer = {
val bundle = Bundle().apply {
putString("ViewModelType", "vm2")
}
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundle)
}
}) {
MyViewModel.Factory
}
...
}
ViewModel Code
#HiltViewModel
class MyViewModel #Inject constructor(
application: Application,
private val myRepo: MyRepository,
private val savedStateHandle: SavedStateHandle
) : AndroidViewModel(application) {
...
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val defaultArgs = extras[DEFAULT_ARGS_KEY]
println("extras $extras and default $defaultArgs")
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
savedStateHandle.keys().forEach {
println("factory $it, ${savedStateHandle.get<Any>(it)}")
}
return MyViewModel(
application = application,
myRepo = MyRepository(application),
savedStateHandle = savedStateHandle
) as T
}
}
}
}
When I print out the default arguments, the first initialized viewmodel is always returned, and is not initialized again even with both variables in the activity having different default arguments. Expected result: New viewmodel instance with different default arguments.
I think it has to do with the Viewmodel store owner key being the same, but I do want the viewmodel store owner to be the same, just as a new instance, if that makes sense.
I know that in the past you could use AbstractSavedStateViewModelFactory, or a custom viewmodel factory with ViewModelProvider.get(), but I can't access ViewModelProvider.get without passing a ViewModelStoreOwner, and since I don't want to pass it to the factory since it could leak the activity, I'm confused as to how to go about this. Is there a better way than using hilt to create multiple instances of the same type of viewmodel in the same scope?
override val viewModel: MyViewModel by activityViewModels()
Create instance of viewModel which lives with activity.

Variables inside a constructor should be private or public in Kotlin

I would like to understand whether variables inside a constructor should be private or public in Kotlin.
What is the significance of having access to modifiers inside the class constructor?
In the below code snippet, the variable service and query are private.
What is the use of keeping them private?
How does it help?
class GithubPagingSource(
private val service: GithubService,
private val query: String
) : PagingSource<Int, Repo>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
TODO("Not yet implemented")
}
}
Note: I have read multiple questions and answers related to this area on Stack overflow but could not find any valid answer.
The thing to consider is that definition of constructors in kotlin is different than java. In your provided snippet, the class has a primary constructor. According to the kotlin docs:
The primary constructor is part of the class header: it goes after the class name (and optional type parameters).
For example:
class GithubPagingSource(
service: GithubService,
query: String
)
also:
Note that parameters of the primary constructor can be used in the initializer blocks. They can also be used in property initializers declared in the class body.
So, what should we do if we want to use them inside the body of a function?
In this case, we have to declare class properties by adding var or val to the parameters of the primary constructor:
class GithubPagingSource(
val service: GithubService,
val query: String
) : PagingSource<Int, Repo>() {
init {
println("$query") // query is accessible here
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
println("$query") // query also is accessible here
}
}
Now, service and query are playing two roles:
first as constructor parameters
second as class variables
On the other hand, the encapsulation principle tells us to keep class variables as much restricted as possible. It means that you should keep them private unless there is a need to be visible from outside.
class GithubPagingSource(
private val service: GithubService,
private val query: String
) : PagingSource<Int, Repo>() {
...
}
So, if we have a reference to an instance of this class:
val githubPagingSource = ...
val query = githubPagingSource.query // is not accessible here

Lazy initialization with repository

I checked out the Android Sunflower best practices app. And i kinda struggle to understand why the initialization of the PlantDetailsViewModel is working.
The class is defined as follows
class PlantDetailViewModel #AssistedInject constructor(
plantRepository: PlantRepository,
private val gardenPlantingRepository: GardenPlantingRepository,
#Assisted private val plantId: String
) : ViewModel() {
val isPlanted = gardenPlantingRepository.isPlanted(plantId)
val plant = plantRepository.getPlant(plantId)
....
#AssistedInject.Factory
interface AssistedFactory {
fun create(plantId: String): PlantDetailViewModel
}
companion object {
fun provideFactory(
assistedFactory: AssistedFactory,
plantId: String
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return assistedFactory.create(plantId) as T
}
}
}
}
here the gardenPlantingRepository and plantRepository give access to a Room Database.
I wonder that this works because normally it is not possible to access a room database on the main thread. The Viemodel is used in the PlantDetailFragment and lazy initialized.
#Inject
lateinit var plantDetailViewModelFactory: PlantDetailViewModel.AssistedFactory
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
PlantDetailViewModel.provideFactory(
plantDetailViewModelFactory,
args.plantId
)
}
if i try something that is quite the same i always get the problem that it is not possible to access the database on the main thread. so i tried to init my variables in the init function with a coroutine and Dispatchers.IO but the problem with this is that when i access member variables of my viewmodel they are not initialized. so how is the behaviour of the sunflower app reproducible
Repository uses Dao methods that return LiveData
The Room persistence library supports observable queries, which return LiveData objects. Observable queries are written as part of a Database Access Object (DAO).
Room generates all the necessary code to update the LiveData object when a database is updated. The generated code runs the query asynchronously on a background thread when needed. This pattern is useful for keeping the data displayed in a UI in sync with the data stored in a database.
https://developer.android.com/topic/libraries/architecture/livedata#use_livedata_with_room
If you want to use coroutines then you should create and expose LiveData in your ViewModel
class ViewModel(
val repository: Repository
) : ViewModel() {
private val _liveData = MutableLiveData<SomeType>()
val liveData: LiveData<SomeType> get() = _liveData
init {
viewModelScope.launch(Dispatchers.IO) {
_liveData.postValue(repository.getSomeData())
}
}
}
Then you should observe this liveData in your activity / fragment

How should I get Resources(R.string) in viewModel in Android (MVVM and databinding)

I am currently using databinding and MVVM architecture for android. What would be the best way to get string resources in ViewModel.
I am not using the new AndroidViewModel component, eventbus or RxJava
I was going through the aproach of interfaces where Activity will be responsible for providing resources. But recently I found a similar question with this answer where a single class using application context is providing all resources.
Which would be the better approach? or is there something else that I can try?
You can access the context by implementing AndroidViewModel instead of ViewModel.
class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getSomeString(): String? {
return getApplication<Application>().resources.getString(R.string.some_string)
}
}
You can also use the Resource Id and ObservableInt to make this work.
ViewModel:
val contentString = ObservableInt()
contentString.set(R.string.YOUR_STRING)
And then your view can get the text like this:
android:text="#{viewModel.contentString}"
This way you can keep the context out of your ViewModel
Not at all.
Resource string manipulation belongs the View layer, not ViewModel layer.
ViewModel layer should be free from dependencies to both Context and resources. Define a data type (a class or enum) that ViewModel will emit. DataBinding has access to both Context and resources and can resolve it there. Either via #BindingAdapter (if you want the clean look) or a plain static method (if you want flexibility and verbosity) that takes the enum and Context and returns String : android:text="#{MyStaticConverter.someEnumToString(viewModel.someEnum, context)}". (context is synthetic param in every binding expression)
But in most cases, String.format is enough to combine resource string format with data provided by ViewModel.
It may seem like "too much code in XML", but XML and bindings are the View layer. The only places for view logic, if you discard god-objects: Activities and Fragments.
//edit - more detailed example (kotlin):
object MyStaticConverter {
#JvmStatic
fun someEnumToString(type: MyEnum?, context: Context): String? {
return when (type) {
null -> null
MyEnum.EENY -> context.getString(R.string.some_label_eeny)
MyEnum.MEENY -> context.getString(R.string.some_label_meeny)
MyEnum.MINY -> context.getString(R.string.some_label_miny)
MyEnum.MOE -> context.getString(R.string.some_label_moe)
}
}
}
usage in XML:
<data>
<import type="com.example.MyStaticConverter" />
</data>
...
<TextView
android:text="#{MyStaticConverter.someEnumToString(viewModel.someEnum, context)}".
For more complicated cases (like mixing resource labels with texts from API) instead of enum use sealed class that will carry the dynamic String from ViewModel to the converter that will do the combining.
For simplest cases, like above, there is no need to invoke Context explicitly at all. The built-in adapter already interprets binding int to text as string resource id. The tiny inconvenience is that when invoked with null the converter still must return a valid ID, so you need to define some kind of placeholder like <string name="empty" translatable="false"/>.
#StringRes
fun someEnumToString(type: MyEnum?): Int {
return when (type) {
MyEnum.EENY -> R.string.some_label_eeny
MyEnum.MEENY -> R.string.some_label_meeny
MyEnum.MINY -> R.string.some_label_miny
MyEnum.MOE -> R.string.some_label_moe
null -> R.string.empty
}
}
Quick-and-dirty hack would be to emit a #StringRes Int directly, but that makes ViewModel dependent on resources.
"Converters" (a collection of unrelated, static and stateless functions) is a pattern that I use a lot. It allows to keep all the Android's View-related types away from ViewModel and reuse of small, repetitive parts across entire app (like converting bool or various states to VISIBILITY or formatting numbers, dates, distances, percentages, etc). That removes the need of many overlapping #BindingAdapters and IMHO increases readability of the XML-code.
an updated version of Bozbi's answer using Hilt
ViewModel.kt
#HiltViewModel
class MyViewModel #Inject constructor(
private val resourcesProvider: ResourcesProvider
) : ViewModel() {
...
fun foo() {
val helloWorld: String = resourcesProvider.getString(R.string.hello_world)
}
...
}
ResourcesProvider.kt
#Singleton
class ResourcesProvider #Inject constructor(
#ApplicationContext private val context: Context
) {
fun getString(#StringRes stringResId: Int): String {
return context.getString(stringResId)
}
}
Just create a ResourceProvider class that fetch resources using Application context. In your ViewModelFactory instantiate the resource provider using App context. You're Viewmodel is Context free and can be easily testable by mocking the ResourceProvider.
Application
public class App extends Application {
private static Application sApplication;
#Override
public void onCreate() {
super.onCreate();
sApplication = this;
}
public static Application getApplication() {
return sApplication;
}
ResourcesProvider
public class ResourcesProvider {
private Context mContext;
public ResourcesProvider(Context context){
mContext = context;
}
public String getString(){
return mContext.getString(R.string.some_string);
}
ViewModel
public class MyViewModel extends ViewModel {
private ResourcesProvider mResourcesProvider;
public MyViewModel(ResourcesProvider resourcesProvider){
mResourcesProvider = resourcesProvider;
}
public String doSomething (){
return mResourcesProvider.getString();
}
ViewModelFactory
public class ViewModelFactory implements ViewModelProvider.Factory {
private static ViewModelFactory sFactory;
private ViewModelFactory() {
}
public static ViewModelFactory getInstance() {
if (sFactory == null) {
synchronized (ViewModelFactory.class) {
if (sFactory == null) {
sFactory = new ViewModelFactory();
}
}
}
return sFactory;
}
#SuppressWarnings("unchecked")
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MainActivityViewModel.class)) {
return (T) new MainActivityViewModel(
new ResourcesProvider(App.getApplication())
);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
You can use the Resource Id to make this work.
ViewModel
val messageLiveData= MutableLiveData<Any>()
messageLiveData.value = "your text ..."
or
messageLiveData.value = R.string.text
And then use it in fragment or activity like this:
messageLiveData.observe(this, Observer {
when (it) {
is Int -> {
Toast.makeText(context, getString(it), Toast.LENGTH_LONG).show()
}
is String -> {
Toast.makeText(context, it, Toast.LENGTH_LONG).show()
}
}
}
Ideally Data Binding should be used with which this problem can easily be solved by resolving the string inside the xml file. But implementing data binding in an existing project can be too much.
For a case like this I created the following class. It covers all cases of strings with or without arguments and it does NOT require for the viewModel to extend AndroidViewModel and this way also covers the event of Locale change.
class ViewModelString private constructor(private val string: String?,
#StringRes private val stringResId: Int = 0,
private val args: ArrayList<Any>?){
//simple string constructor
constructor(string: String): this(string, 0, null)
//convenience constructor for most common cases with one string or int var arg
constructor(#StringRes stringResId: Int, stringVar: String): this(null, stringResId, arrayListOf(stringVar))
constructor(#StringRes stringResId: Int, intVar: Int): this(null, stringResId, arrayListOf(intVar))
//constructor for multiple var args
constructor(#StringRes stringResId: Int, args: ArrayList<Any>): this(null, stringResId, args)
fun resolve(context: Context): String {
return when {
string != null -> string
args != null -> return context.getString(stringResId, *args.toArray())
else -> context.getString(stringResId)
}
}
}
USAGE
for example we have this resource string with two arguments
<string name="resource_with_args">value 1: %d and value 2: %s </string>
In ViewModel class:
myViewModelString.value = ViewModelString(R.string.resource_with_args, arrayListOf(val1, val2))
In Fragment class (or anywhere with available context)
textView.text = viewModel.myViewModelString.value?.resolve(context)
Keep in mind that the * on *args.toArray() is not a typing mistake so do not remove it. It is syntax that denotes the array as Object...objects which is used by Android internaly instead of Objects[] objects which would cause a crash.
I don't use data bindig but I guess you can add an adapter for my solution.
I keep resource ids in the view model
class ExampleViewModel: ViewModel(){
val text = MutableLiveData<NativeText>(NativeText.Resource(R.String.example_hi))
}
and get text on a view layer.
viewModel.text.observe(this) { text
textView.text = text.toCharSequence(this)
}
You can read more about native text in the article
For old code which you don't want to refactor you can create an ad-hoc class as such
private typealias ResCompat = AppCompatResources
#Singleton
class ResourcesDelegate #Inject constructor(
#ApplicationContext private val context: Context,
) {
private val i18nContext: Context
get() = LocaleSetter.createContextAndSetDefaultLocale(context)
fun string(#StringRes resId: Int): String = i18nContext.getString(resId)
fun drawable(#DrawableRes resId: Int): Drawable? = ResCompat.getDrawable(i18nContext, resId)
}
and then use it inside your AndroidViewModel.
#HiltViewModel
class MyViewModel #Inject constructor(
private val resourcesDelegate: ResourcesDelegate
) : AndroidViewModel() {
fun foo() {
val helloWorld: String = resourcesDelegate.string(R.string.hello_world)
}
If you are using Dagger Hilt then #ApplicationContext context: Context in your viewModel constructor will work. Hilt can automatically inject application context with this annotation. If you are using dagger then you should provide context through module class and then inject in viewModel constructor. Finally using that context you can access the string resources. like context.getString(R.strings.name)
You should use something like "UIText" sealed class to mange it all over your project(as Philip Lackner have done).
sealed class UIText {
data class DynamicString(val value:String):UIText()
class StringResource(
#StringRes val resId: Int,
vararg val args: Any
):UIText()
#Composable
fun asString():String{
return when(this){
is DynamicString -> value
is StringResource -> stringResource(resId, *args)
}
}
}
Then everyWhere in your project in place of String, use UIText.StringResource easily
Still don't find here this simple solution:
android:text="#{viewModel.location == null ? #string/unknown : viewModel.location}"

LiveData is abstract android

I tried initializing my LiveData object and it gives the error: "LiveData is abstract, It cannot be instantiated"
LiveData listLiveData = new LiveData<>();
In a ViewModel, you may want to use MutableLiveData instead.
E.g.:
class MyViewModel extends ViewModel {
private MutableLiveData<String> data = new MutableLiveData<>();
public LiveData<String> getData() {
return data;
}
public void loadData() {
// Do some stuff to load the data... then
data.setValue("new data"); // Or use data.postValue()
}
}
Or, in Kotlin:
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
viewModelScope.launch {
val result = // ... execute some background tasks
_data.value = result
}
}
}
Since it is abstract (as #CommonsWare says) you need to extend it to a subclass and then override the methods as required in the form:
public class LiveDataSubClass extends LiveData<Location> {
}
See docs for more details
Yes, you cannot instantiate it because it is an abstract class. You can try to use MutableLiveData if you want to set values in the live data object. You can also use Mediator live data if you want to observe other livedata objects.
You need to use MutableLiveData and then cast it to its parent class LiveData.
public class MutableLiveData
extends LiveData
[MutableLiveData is] LiveData which publicly exposes setValue(T) and postValue(T) method.
You could do something like this:
fun initializeLiveData(foo: String): LiveData<String> {
return MutableLiveData<String>(foo)
}
So then you get:
Log.d("now it is LiveData", initializeLiveData("bar").value.toString())
// prints "D/now it is LiveData: bar"
I think much better way of achieving this is by using, what we call is a Backing Property, to achieve better Encapsulation of properties.
Example of usage:
private val _userPoints = MutableLiveData<Int>()// used as a private member
val userPoints: LiveData<Int>
get() {
return _userPoints
} //used to access the value inside the UI controller (Fragment/Activity).
Doing so maintains an editable MutableLiveData private to ViewModel class while, the read-only version of it is maintained as a LiveData, with a getter that returns the original value.
P.S. - notice the naming convention for both fields, using an (_) underscore. This is not mandatory but advised.

Categories

Resources