Best way to use a Global String variable in Android App 2022? - android

I have a variable globalStringVar which I use throughout my application, including in different repositories in the app's data layer. I am trying to work out the best/cleanest way to pass this around my application.
Currently globalStringVar is instantiated in my Application() class. I then use Application().getAppInstance().myGlobalString in the repositories when I need to use it. I also update the variable in the repositories.
I feel like there is code smell here and a better way exists to store and use a global string variable. I tried to see if I could somehow inject the string using dagger/hilt but had no success. Does anybody have a better way of using global string variables?
#HiltAndroidApp
class Application : android.app.Application() {
var globalStringVar = "Start Value"
companion object {
private lateinit var applicationInstance: Application
#JvmStatic
fun getAppInstance(): Application {
return applicationInstance
}
}
override fun onCreate() {
super.onCreate()
applicationInstance = this
}
}
#Singleton
class MyRepo #Inject constructor() {
fun updateValue() {
Application.getAppInstance().globalStringVar = "Update Value"
}
/*other repo code*/
}

I have created my own custom class MyData() that holds globalString property.
This can then be injected where necessary with Dagger/Hilt.
class MyData() {
var globalString: String = "Start Value"
}
#Module
#InstallIn(SingletonComponent::class)
object DataModule {
#Singleton
#Provides
fun provideDataInstance(): MyData {
return MyData()
}
}
#Singleton
class MyRepo #Inject constructor() {
#Inject lateinit var myData: MyData
fun updateValue() {
myData.globalString = "Update Value"
}
/*other repo code*/
}

Related

Dagger2 Provider in koin

Is there any alternative to javax.inject.Provider in koin?
To react to actions, I am injecting Commands to my activity.
Command is a single-run object, for example WriteToFile.
In dagger I could make it like this:
class MainPresenter : Presenter() {
#Inject
lateinit var writeFile: Provider<WriteFileCommand>
fun onSaveClicked() {
writeFile.get().run()
}
}
in koin, when I try to use:
class MainPresenter : Presenter() {
lateinit var writeFile: Provider<WriteFileCommand> by inject()
fun onSaveClicked() {
writeFile.get().run()
}
}
My koin module:
val appModule = module {
factory { WriteFileCommand(get(), get()) }
factory { FileProvider() }
single { DataStore() }
}
Than I got error saying:
Can't create definition for 'Factory [name='WriteFileCommand',class='com.test.WriteFileCommand']' due to error :
No compatible definition found. Check your module definition
I understand that I can call:
var command: WriteFileCommand = StandAloneContext.getKoin().koinContext.get()
command.run()
But It looks so cumbersome
There's nothing like a provider directly. If you use inject, you'll use a lazy delegate. If you use get, you'll create a new instance you declared the dependency with a factory. So get is what you need in your case. Just let your MainPresenter implement KoinComponent and you'll be able to use get directly:
class MainPresenter : Presenter(), KoinCompontent {
fun onSaveClicked() = get<WriteFileCommand>().run()
}

Dagger 2 Injecting parameters in generic class

I have provided a dependency FirebaseFirestore in a module and now I am trying to write a generic class and I need to inject FirebaseFirestore dependency here but not sure how to do it. here it is what I am doing right now:
class PersistenceStore<T> : IPersistenceStore<T> {
#Inject
lateinit var db: FirebaseFirestore
override fun addRow(item: T): T {
}
}
Guide me to the right path please.
I assume you know how to inject normally.
First solution. Not so generic. Defeats the purpose of generic.
#Singleton
#Component(modules = [FireBaseFirestoneModule::class])
interface FireBaseComponent {
fun inject(PersistenceStore<ActualItemClass> obj) // just put the actual item type in here.
}
Second solution. Wrapper. Inject the wrapper instead.
class PersistenceStore<T> : IPersistenceStore<T> {
private val injectWrapper = InjectWrapper()
constructor() {
DaggerFireBaseComponent().builder().build()
.inject(injectWrapper) // inject the wrapper
}
override fun addRow(item: T): T {
}
}
class InjectWrapper { // put all injectable variables here
#Inject
lateinit var db: FirebaseFirestore
}

Dagger-2 "lateinit property application has not been initialized"

I am trying to inject application context in a class which is giving
”lateinit property application has not been initialized"
exception.
CoreModule.kt
#Module
open class CoreModule {
#Singleton
#Provides
fun provideRealmHelper(): RealmHelper {
return RealmHelper()
}
}
MyApplication.kt
open class MyApplication : MultiDexApplication(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
val log = LoggerFactory.getLogger(this.javaClass)!!
companion object {
var application: MyApplication? = null
fun getInstance(): MyApplication {
return application!!
}
}
override fun onCreate() {
try {
super.onCreate()
application = this
DaggerAppComponent.builder().application(this).build().inject(this)
} catch (e: Exception) {
log.error("Exception in Application", e)
Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionHandler())
}
}
override fun activityInjector() = dispatchingAndroidInjector
}
AppComponent.kt
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class,CoreModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: MyApplication): Builder
fun build(): AppComponent
}
fun inject(myApplication: MyApplication)
fun inject(realmHelper: RealmHelper)
}
//I need application context in this class. I am inject applicationContext here.
Is injecting is correct way to do or I should use constructor injection?
RealmHelper.kt
class RealmHelper #Inject constructor() {
//need application context here but getting "lateinit property application has not been initialized
#Inject
lateinit var application: MyApplication
init {
Realm.init(application) // null application
}
}
Note: MyApplication is added to AndoridManifest.xml
The problem is you annotated your field but not injected. You can inject field like you did in application class : DaggerAppComponent.builder().application(this).build().inject(this)
or you can move your application field to RealmHelper constructor and in core module you need to write a provide function to return application. If you want to see an example I have an applicaton. https://github.com/volkansahin45/Moneycim
I need application context in this class. I am inject
applicationContext here. Is injecting is correct way to do or I should
use constructor injection?
Always favor constructor injection over field injection if possible.
Your CoreModule is not needed. The code below is enough.
#Singleton
class RealmHelper #Inject constructor(private val application: MyApplication) {
//Your implementation
}
fun inject(realmHelper: RealmHelper) in your Component is also unnecessary.
Removing those lines should fix it, I quickly threw together a demo project just to test it to make sure. Here is a quick gist with the code.
this might be too late but this may help other developer..
#set:Inject
internal var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>? = null
use this instead of
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
and in case you get same error elsewhere, do the same. Use #set:Inject instead of #Inject and use internal instead of lateinit.
This worked for me like charm.

Using Dagger 2 singletons in View Models on Android

I want my application to have the following architecture :
-di // dependancy injection package
-model
--datasources // implementations of repositories, using dao to return data
--dao // access to the database (room)
--repositories // interfaces
-ui // activities, fragments...
-viewmodels // viewmodels of each fragment / activity which will return data to the views in the ui package
On Android, people seem to usually have the dependancy injection in the activities, but I want to inject my repositories in view models.
I have one particular viewmodel that uses a manually created LiveData
class NavigationViewModel(application: Application) : AndroidViewModel(application) {
init {
DaggerAppComponent.builder()
.appModule(AppModule(getApplication()))
.roomModule(RoomModule(getApplication()))
.build()
.injectNavigationViewModel(this)
}
#Inject
lateinit var displayScreenRepository: DisplayScreenRepository
fun setScreenToDisplay(screen: DisplayScreen) {
displayScreenRepository.setScreenToDisplay(screen)
}
}
In my RoomModule, I have this provider :
#Singleton
#Provides
internal fun displayScreenRepository(): DisplayScreenRepository {
return DisplayScreenDataSource()
}
The class DisplayScreenDataSource is very simple :
class DisplayScreenDataSource : DisplayScreenRepository {
private val screenToDisplay = MutableLiveData<DisplayScreen>()
override fun getScreenToDisplay(): LiveData<DisplayScreen> {
return screenToDisplay
}
override fun setScreenToDisplay(screen: DisplayScreen) {
if (Looper.myLooper() == Looper.getMainLooper()) {
screenToDisplay.value = screen
} else {
screenToDisplay.postValue(screen)
}
}
}
I want this singleton to be available to an other viewmodel, MainActivityViewModel, so I did this :
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
#Inject
lateinit var displayScreenRepository: DisplayScreenRepository
init {
DaggerAppComponent.builder()
.appModule(AppModule(application))
.roomModule(RoomModule(application))
.build()
.injectMainViewModel(this)
}
}
But when I run my application, the repositories instantiated don't have the same reference and when I update the value of one LiveData in one of my ViewModel, if I observe the LiveData of an other ViewModel, it is not the same LiveData so is is not updated.
My guess is that I am not correctly instantiating the DaggerAppComponent : it is supposed to be created only once and eventually injected several times.
Is that right?
Where should I be supposed to store the instance of the DaggerAppComponent? In the Application class?
I could have something like that :
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
#Inject
lateinit var displayScreenRepository: DisplayScreenRepository
init {
(application as MyApplication).daggerAppComponent.injectMainViewModel(this)
}
}
Is this the recommended way?

Kotlin singleton application class

On Android I want to make my application class a singleton.
Making it like this:
object MyApplication: Application(){}
won't work. The following error is thrown at runtime:
java.lang.IllegalAccessException: private com....is not accessible from class android.app.Instrumentation.
Doing this is also not possible:
class MyApp: Application() {
private val instance_: MyApp
init{
instance_ = this
}
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree());
}
}
companion object{
fun getInstance() = instance_
}
}
How can I get an instance of my application class everywhere in my app? I would like to use MyApp.instance() instead of (applicationContext as MyApp).
Also an explanation why I want this: I have classes in my app. For example, a SharedPreference Singleton which is initialised with a context, and as it’s a singleton, it can't have arguments.
You can do the same thing you would do in Java, i.e. put the Application instance in a static field. Kotlin doesn't have static fields, but properties in objects are statically accessible.
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: MyApp
private set
}
}
You can then access the property via MyApp.instance.
If you want to use it to access some static properties you have there: You will only have one instance of your Application, so simply use the name you gave to the class. Don't worry about it not being an actual singleton, you can use it the same way.
Example:
class MyApp : Application() {
companion object {
const val CONSTANT = 12
lateinit var typeface: Typeface
}
override fun onCreate() {
super.onCreate()
typeface = Typeface.createFromAsset(assets, "fonts/myFont.ttf")
}
}
Then you can use MyApp.CONSTANT and MyApp.typeface anywhere in your app.
-
If what you want is to use it as an application context you can create an extension property for Context:
val Context.myApp: MyApp
get() = applicationContext as MyApp
Then you can use myApp to get the the application context anywhere you have a context.
class AppController : Application() {
init {
instance = this
}
companion object {
private var instance: AppController? = null
fun applicationContext() : AppController {
return instance as AppController
}
}
override fun onCreate() {
super.onCreate()
}
}
You cannot do that because Android creates an Application instance using its parameterless constructor.
The problem you want to solve can be easily solved with DI. Just create instances with an injector so that the Context can be injected into objects as a dependency.

Categories

Resources