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.
Related
I have a singleton that I'm using to open a JSON asset and return it as a list. I need to access the application context in order to use the Asset Manager. I can't pass in the context because I'm calling it from a view model, which does not have access to the application context. I've done a lot of searching but I can't seem to find the answer.
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
object ProgramListService {
fun getProgramList(): List<ProgramList>? {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val json = context.assets.open("programs/home.json").bufferedReader().use{ it.readText() }
val listType = Types.newParameterizedType(List::class.java, ProgramList::class.java)
val adapter: JsonAdapter<List<ProgramList>> = moshi.adapter(listType)
return adapter.fromJson(json)
}
}
Context is not good in a ViewModel, not even application context, nor is AndroidViewModel good to use. So you can pass AssetManager into your ViewModel which can then pass it into your ProgramListService, avoiding the need for context, but asset manager is still a bit weird to have in view model.
So you can skip the viewmodel and pass the application context directly into your singleton ProgramListService.
object ProgramListService {
lateinit var application: Application // Add this
...
}
And then from your activity's onCreate or wherever is best for your project,
ProgramListService.application = context.applicationContext as Application
Or like #Tenfour04 suggested application is safe to make a global property
You have the Context in ViewModel
public class HomeViewModel extends AndroidViewModel {
public HomeViewModel(Application application) {
super(application);
Context context = getApplication().getApplicationContext();
}}
I would hesitate to call what you have a singleton, since it doesn't carry any state. You've used an object to organize the namespace of what would be a static method in Java.
If your ViewModel is an AndroidViewModel, it comes with an Application instance, so you can use that as your context:
class MyViewModel(val application: Application): AndroidViewModel(application) {
fun foo() {
someRepoAccessCall(application)
}
}
Since the Application is safe to use as a singleton, you can alternatively create a global property for it that you initially set in onCreate().
lateinit var application: MyApplication
class MyApplication(): Application
override fun onCreate() {
super.onCreate()
application = this
}
}
Don't forget to assign the custom Application class in the manifest:
<application
android:name=".MyApplication"
...
I need in my Singleton -> Context. I know that I can't passing argument in constructor, because object hasn't constructor.
Then I call it from my Application class.
Here is the code:
object Singleton {
var userAgentInfo: String = UserAgentTools.buildUserAgent(context)
fun initializeSdk() {
AuthenticatorApiManager.initializeSdk(userAgentInfo)
}
}
Move the initialization of userAgentInfo to the initializeSDK method, and send the Context as an argument, make sure to send the ApplicationContext.
object Singleton {
var userAgentInfo: String? = null
fun initializeSdk(context: Context) {
userAgentInfo = UserAgentTools.buildUserAgent(context)
AuthenticatorApiManager.initializeSdk(userAgentInfo)
}
}
Make Application class and write below code.
companion object {
private lateinit var sInstance: ApplicationClass
fun getInstance(): ApplicationClass {
return sInstance
}
}
Use in object like below.
ApplicationClass.getInstance()
You can use context in your Singleton class using Application class instance.here it is
I would like to define 2 injected classes, but one needs to use the second class method for the constructor. I am using Koin framework
class MainActivity : AppCompatActivity() {
private val connectionService : ConnectionService by inject()
private val resourcesHelper : ResourcesHelper by inject()
private val addressPropertyName = "connection.address"
private val portPropertyName = "connection.port"
private val appModule = module {
single { ResourcesHelperImpl(androidContext(), R.raw.config) }
single {
ConnectionServiceTcp(
resourcesHelper.getConfigValueAsString(addressPropertyName),
resourcesHelper.getConfigValueAsInt(portPropertyName)
)
}
}
And then I get an error because I cannot instantiate ConnectionServiceTcp using resourcesHelper. Is there a way to use injected field to inject another field?
Edit
Changing to get() helped, but now I struggle with module configuration.
I moved start koin to MainApplication class:
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MainApplication)
androidLogger()
modules(appModule)
}
}
}
And module to AppModule.kt
val appModule = module {
single { ResourcesHelperImpl(androidContext(), R.raw.drone) }
single {
ConnectionServiceTcp(
get<ResourcesHelper>().getConfigValueAsString(ResourcesHelper.droneAddressPropertyName),
get<ResourcesHelper>().getConfigValueAsInt(ResourcesHelper.dronePortPropertyName)
)
}
scope(named<MainActivity>()) {
scoped {
ConnectionServiceTcp(get(), get())
}
}
}
And then I try to inject some object to activities and I am getting
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for has been found. Check your module definitions.
Okay, I encountered two problems, firstly I could not instantiate bean using other bean in constructor, it was resolved by changing my invoke to
ConnectionServiceTcp(
get<ResourcesHelper>().getConfigValueAsString(ResourcesHelper.droneAddressPropertyName),
get<ResourcesHelper>().getConfigValueAsInt(ResourcesHelper.dronePortPropertyName)
)
Secondly, there was a problem with NoBeanDefFoundException, it was due androidContext() in ResourcesHelperImpl, I needed there Context from the activity, not the koin context.
I have created an application class in Kotlin. I need to access a method that returns a variable from anywhere in my application. The problem is I am not able to access that method from other parts of the program. I am able to access when code is written in Java , but when code is written in Kotlin,then the method in Application class is not accessible. Please find below code for reference:
class MyRetroApplication : Application() {
lateinit var apiComponent:APIComponent
companion object {
var ctx: Context? = null
}
override fun onCreate() {
super.onCreate()
ctx = applicationContext
apiComponent = initDaggerComponent()
}
fun getMyComponent(): APIComponent {
return apiComponent
}
fun initDaggerComponent():APIComponent{
apiComponent = DaggerAPIComponent
.builder()
.aPIModule(APIModule(APIURL.BASE_URL))
.build()
return apiComponent
} }
In the above code how to access the function getMyComponent() globally in Kotlin.
Put getMyComponen() inside companion like #Md. Asaduzzaman answer or use applicationContext to access it like -
(application as MyRetroApplication).getMyComponent()
or
(applicationContext as MyRetroApplication).getMyComponent()
or
MyRetroApplication.ctx?.let{
(it as MyRetroApplication).getMyComponent() //by your companion app context
}
Approach-1:
Put getMyComponent() inside companion
companion object {
var ctx: Context? = null
private lateinit var apiComponent: APIComponent
fun getMyComponent(): APIComponent = apiComponent
}
And then from anywhere:
MyRetroApplication.getMyComponent()
Approach-2:
Change the type of ctx to MyRetroApplication instead of Context and then from anywhere:
MyRetroApplication.ctx.getMyComponent()
Approach-3:
Same as approach 2 but in a formal way. Create getInstance() inside companion and pass ctx (private)
companion object {
private lateinit var ctx: MyRetroApplication
fun getInstance(): MyRetroApplication {
return ctx
}
}
And then from anywhere:
MyRetroApplication.getInstance().getMyComponent()
I am trying to add a "static" method to my MyApplication class in kotlin
I have added (as a property) the variable :
private var context: Context? = null
in method:
override fun onCreate()
I added:
context = applicationContext
then I add a companion object like this
companion object {
#JvmStatic fun getMyApplicationContext(): Context?
{
return MyApplication().context
}
}
when I call this method from other parts of the application like
MyApplication.getMyApplicationContext() it always returns null. I have gleaned all this from several sources but I am not sure if it is anywhere near correct or not.
It sounds like you want a global application context object. Now casting aside my dislike for global variables, I think you are pretty close.
I think you just need to add the variable into the MyApplication classes companion object and use that directly. You only need the #JvmField annotation if you're going to access the field from Java.
class MyApplication {
companion object {
#JvmField
var context: Context? = null
// Not really needed since we can access the variable directly.
#JvmStatic fun getMyApplicationContext(): Context? {
return context
}
}
override fun onCreate() {
...
MyApplication.context = appContext
}
}