Memory-leak free Singleton with context - android

I am trying to implement the following singleton pattern: SingletonClass.getInstance(context).callMethod()
While there are a variety of tutorials that explain how to make singletons in Kotlin, none of them address the fact that holding a context in a static field will cause memory leaks in Android.
How do I create the above pattern without creating a memory leak?
Update:
Here is my implementation of CommonsWare's solution #2. I used Koin.
Singleton class:
class NetworkUtils(val context: Context) {
}
Application Class:
class MyApplication : Application() {
val appModule = module {
single { NetworkUtils(androidContext()) }
}
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(appModule))
}
}
Activity class:
class MainActivity : AppCompatActivity() {
val networkUtils : NetworkUtils by inject()
}

Option #1: Have getInstance(Context) call applicationContext on the supplied Context and hold that. The Application singleton is created when your process is and lives for the life of the process. It is pre-leaked; you cannot leak it further.
Option #2: Get rid of getInstance() and set up some form of dependency injection (Dagger 2, Koin, etc.). There are recipes for these DI frameworks to have them supply the Application singleton to the singletons that they create and inject downstream.

When you call the getInstance() for the first time, the Context that you pass to this function is saved forever. So, the context in further getInstance() calls doesn't have anything to do there. I never save this Context.
This is what I am doing:
Create an object in Kotlin and initialize the object with a context as soon as the app starts. Instead of storing the context, I perform whichever operation is required with that context.
object PreferenceHelper {
private var prefs: SharedPreferences? = null
fun initWith(context: Context){
if(prefs == null) this.prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
}
fun someAction(){ .... }
}
and inside the Application class:
class MyApp: Application(){
override fun onCreate(){
PreferenceHelper.initWith(this)
}
}
and later anywhere in the app:
PreferenceHelper.someAction()
You can do this if you don't need a reference to the Context every time you perform something with the Singleton class.

I would not store the context in the SingletonClass, I would simply pass the context to each method of the class via dependency injection.
Something like:
SingletonClass.callMethod(context)
Define the "static" method in the companion object like that:
companion object {
fun callMethod(context: Context) {
// do Something
}
}
Then call it from your activity with:
SingletonClass.callMethod(this)
Hope that it helps :)

if You must create singleton class with context involved, you can do it like this. This will help. In this case, your context will be reset in every activity, when you call getInstance(context).
public class MyClass {
private Context context;
public static getInstance(Context context){
if(instance ==null)
instance = new MyClass();
instance.setContext(context);
return instance;
}
public void setContext(Context context){
this.context = context;
}
}

Related

How to pass context in Repository in MVVM without DI and following the clean architecture?

I need to initiate Room in its repository. But to do that I need context. If I pass context through the viewmodel i got this message This field leaks a context object.
I have checked this answer but they init the repository object in the view layer but according to the clean architecture view layer shouldnt know anything about data layer right? So how to organize the delivery of the context to the data layer without DI?
class MainViewModel(private val context: Context) : ViewModel() {
private val roomManager : RoomManager = RoomManagerImpl(context)
private fun addItem(){
roomManager.addItem()
}
}
Here is repository code
class RoomManagerImpl(private val context: Context) : RoomManager {
private val db = Room.databaseBuilder(
context,
AppDatabase::class.java, "database-name"
)
Passing Activity Context to the Activity's ViewModel is not a good practice it will cause memory leak.
You can get the context in your ViewModel by extending the AndroidViewModel class check below code.
It will give you application level context.
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
private val roomManager: RoomManager = RoomManagerImpl(context)
fun addItem() {
roomManager.addItem()
}
}
class MainViewModal(val repository:roomManager) :ViewModel() {
fun testFunction(context: Context){
roomManager.testMethod(context) //text method is your repository method
}
}
i am telling like this I hope it's help you otherwise you can use AndroidViewModel instead of Viewmodal

In Hilt, How can I Inject a context in a non activity/ fragment class using field injection?

I have a Exception Handling class
class IOException : BaseException {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface AnalyticsServiceProviderEntryPoint {
fun analyticsService(): AnalyticsService
}
private val hiltEntryPoint = EntryPointAccessors.fromApplication(**Need Context**, AnalyticsServiceProviderEntryPoint::class.java)
val analyticsService = hiltEntryPoint.analyticsService()
}
If I see this offical link, it says
In this example, you must use the ApplicationContext to retrieve the
entry point because the entry point is installed in SingletonComponent
What If I don't have the context in the class and in the function body which I will use and I don't want to use from Constructor Injection as well?
I only want to use the field injection. How can I access it, since I don't have the context.
In your application add a companion object with lateinit var applicationContext and initialize the variable when the application is initialized like:
companion object {
lateinit var applicationContext: Context
private set
then you have an static variable with the application context and you can you something like:
private val hiltEntryPoint = EntryPointAccessors.fromApplication(Application.applicationContext, AnalyticsServiceProviderEntryPoint::class.java)
I can't think of anything else to do what you want to do
Did you try to get applicationContext from dagger-hilt ApplicationContextModule? Its already provided by this module in your app and you can get that probably. You need to use the qualifer also
#Provides
#ApplicationContext
Context provideContext() {
return applicationContext;
}

Access the application context inside of a singleton

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"
...

How i can use context in Singleton?

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

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