How Do I Pass a Context from a Fragment in Kotlin? - android

How do you pass a Context from a running fragment to another class? I need to get resource data out of it, but for some reason, it appears to be null when I send it through the arguments.
class BeatBoxFragment : Fragment() {
...
var mBeatBox: BeatBox = BeatBox(this.context!!)
...
}
I will be using the context of this fragment to access assets from the activity.
class BeatBox(context: Context) {
...
val mAssets: AssetManager = context.getAssets()
...
}
The program crashes if I try to call BeatBox with the arguments this.context!!. Apparently it is null (according to what my stack trace suggests: android.beatbox.BeatBoxActivity}: kotlin.KotlinNullPointerException")
Any help is greatly appreciated.

Use requireContext() which ensure that Fragment is attached and returns a valid non-null Context which we can use without any trouble.
Also you can use applicationContext to access asset which is not depends on Activity Life Cycle

Related

Is there difference between `Class.newInstance()` and `Class()` in Kotlin?

Is there a any functional difference in Kotlin between those?
class Foo {
companion object {
fun newInstance() = Foo()
}
}
// Create new instance somewhere else
val foo = Foo.newInstance()
and
class Foo {
}
// Create new instance somewhere else
val foo = Foo()
I think that the first approach is just adding more boilerplate code and taking more memory because of companion object. Am I right?
Note: I am in Android environment and Foo is actually an Fragment in my case, if it matters.
There is no advantage to the first block of code in most cases.
However, in the case of Android Fragments, there is a pattern that you never use the constructor directly to create a Fragment. This is because the Android framework needs to be able to instantiate your Fragment on your behalf when it is recreating the Fragment, and it will only do this by using the empty (zero arguments) constructor. Therefore, when you need to pass arguments to a new Fragment, instead of creating a constructor for that, you create a factory function that creates the fragment with the empty constructor and then passes the arguments by adding them to the Fragment's bundle.
When you don't need any arguments passed to your new Fragment in this way, there's no actual need for the factory function instead of a constructor. However, some people do it that way anyway for consistency. Whether you want to follow this pattern is totally up to you.
At the documentation there is no need to companion objects
https://developer.android.com/guide/fragments/create#kotlin

Lateinit properties were not initialized on Fragment

I have write my first Kotlin and android app and i'm facing a lot of crashes on this app.
All of them are related to the lateinitkeyword.
i get crashes like :
Caused by e.y: lateinit property coordinator has not been initialized
and:
Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfo{app.myapp/mypackage.myapp.Controllers.MainActivity}: e.y: lateinit property coordinator has not been initialized
Caused by e.y
lateinit property coordinator has not been initialized
myapp.com.myapp.Controllers.Fragments.Parents.MyParentFragment.getCoordinator
Last one trace for example is related to a variable i set on my fragment once i initialize it like this :
fun newInstance(mainObject: MyObject, anotherObject: AnotherObject, coordinator: FragmentCoordinator): MyFragment {
val fragment = MyFragment()
fragment.mainObject = mainObject
fragment.anotherObject = ticket
fragment.coordinator = coordinator
return fragment
}
Fragment side looks like :
class MyFragment: MyParentFragment() {
companion object {
fun newInstance(mainObject:...)
}
lateinit var mainObject: MainObject
lateinit var anotherObject: AnotherObject
...
}
What i understand is that when app changes its state (background...) this lateinit properties reference get lost, once the code call this variable property is null and app crash... Please understand that once they fragment is created all this variable are not null, they get allocated.
i have found this article : https://www.bignerdranch.com/blog/kotlin-when-to-use-lazy-or-lateinit/ which indicates what happened and can be fixed linking the variable to the app lifecycle using by lifecycleAwareLazy(lifecycle) thing is at the ends of the article he adds: These two property delegates solve one problem, but not completely.
They still contain a memory leak. Why does he says "they still contains memory leaks ?"
So how on android i can be sure that my variable always be set wether the app comes back from background or whatever, because this happen when the fragment is displayed by the app, some codes run good and then needs to call this lateinit variable and crash because this variable does not exist anymore but existed once the fragment where create.
Thank you for any help.
When your activity gets recreated, FragmentManager invokes 0-argument constructor of each attached Fragment to recreate them as well.
Your newInstance method should only create a Bundle and use it in Fragment.setArguments(Bundle) as this is the only way to ensure those arguments survive configuration changes. Then inside onCreate you can getArguments to retrieve that Bundle.
If you need arguments that cannot be put in a Bundle, you have to inject / recreate / fetch them inside Fragments onCreate or other method.
lateinit indicates that you are not assign the variable value at declaration time but you have to assign the value late in your java class before uses it.
If you tries to get the value of lateinit variable before assignment than lateinit property coordinator has not been initialized exception is occur.
If you are not sure that you lateinit varibale is assign or not you can check the value using
isInitialized method given for lateinit variable to avoid crashes.
Example :
if(::varibleName.isInitialized){
// do something
}
If you want to save the variables values after configuration changes you have to use ViewModel.
If you not uses ViewModel than pass the values by bundle and get the values again from it and re-assign the variables, but note that if you are passing too many values using bundle than you will get the TooLargeTransition exception. You can also used onSaveInstanceState to store the updated values and retrieve it in onCreate method.
fun newInstance(mainObject: MyObject, anotherObject: AnotherObject, coordinator: FragmentCoordinator): MyFragment {
val fragment = MyFragment()
fragment.mainObject = mainObject
fragment.anotherObject = ticket
fragment.coordinator = coordinator
return fragment
}
You cannot create a Fragment like this, you cannot set field variables like this.
The system recreates fragments after low memory condition using no-arg constructor via reflection. Your code, here, newInstance and stuff - that never runs. Never.
On another hand, setArguments(Bundle arguments are retained by the system, and you would be able to get them using getArguments().
So currently, if your app is put to background, killed by Android, and you open the app from launcher again, then you'll crash.
See related questions:
Singleton object becomes null after app is resumed (where I describe how you can reproduce this in dev environment easily)
also maybe App crash after activity has been killed in background

Do not place Android context classes in static fields

I need to access to the context of an application, but doing it this way it says "Do not place Android context classes in static fields" on the third line. Tried to remove the private val but without it I can't access to the context on the copyDatabase function.
I need the context to copy a database in the assets folder to the data folder of the application.
class Database constructor(private val ctx: Context) : ManagedSQLiteOpenHelper(ctx, "dex.db", null, 1) {
companion object {
private var instance: Database? = null
#Synchronized
fun getInstance(ctx: Context): Database {
if (instance == null) {
instance = Database(ctx.applicationContext)
}
return instance!!
}
}
private fun copyDatabase() {
val input = ctx.assets.open("databases/dex.db")
FileOutputStream(ctx.getDatabasePath("dex.db").path).use { out ->
input.copyTo(out)
}
}
}
Thanks
By using a companion object, whose lifetime equals the lifetime of the loaded Database class, you have created a lifecycle mismatch between the Context you capture in the database instance and the database instance itself.
When your application is put to background and restored, or if you just rotate the device, the context (typically the Activity instance) will be destroyed without destroying the whole application, so the database instance will survive with a disposed context. This will reliably lead to application failure.
Although you could be careful to use an Application context instead, which would most probably match the lifecycle, the general practice of retaining the context instance is ill-advised, hence the warning.
Instead put the database instance as a property of your main activity class, or, alternatively, commit to using a dependency injection framework that will deal with this as a separate concern.
As an aside, your current code uses a broken variant of the double-checking lazy initialization idiom. If you need lazy initialization, in Kotlin you should always leave this to the by lazy property delegate instead of rolling your own.
Keeping the Context inside a static variable prevent the garbage collector to free the memory afterwards and will lead to a memory leak. You should pass a Context to your constructor and initialize everything inside it and should not have to retain it inside a variable.

Static reference to application context and resources in a companion object of Application

I've read a number of articles and stackoverflow posts regarding this question (mainly context instead of resources). A lot of people said it's OK because the Application class and the application context should always be alive throughout the life of the app, but some still discourage doing this sighting unknown possibilities (which made me doubtful - hence this post).
On the other hand, passing Context to every Object's function is quite daunting. In Java, one way to overcome this was to do something like UtilClass.getInstance(context) but you can't do that with Kotlin objects and I'm not too sure about implementing Kotlin Singletons with Arguments.
So, I have the following code:
class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
_resources = resources
_context = applicationContext
//..other code
}
companion object {
val resources: Resources
get() = _resources!!
val context: Context
get() = _context!!
private var _resources: Resources? = null
private var _context: Context? = null
}
}
As you can see, I'm keeping a static reference to the application context and resources in a companion object when onCreate is called. With this, I'm able to (as examples):
call App.context in a util object that make API calls with Ion.
call App.resources in a util object that uses resource strings.
It's a convenience as compared to passing Context and/or Resources every time I call an object's method. Is this a safe solution? If not, are there any better alternatives?

Error when trying to load bitmaps

Why do I get the error message The method getResources() is undefined for the type ColorObjectManager?
I use this line to load a Bitmap image:
orange = BitmapFactory.decodeResource(getResources(), R.drawable.pearl_orange);
It's working fine if I'm doing this in another class that I call GameLoop which I make an object of inside the MainActivity class. But it's not working when I trying to do this in the class ColorObjectManager which I make an object of inside the GameLoop class. Do you follow?
Why am I limited to just use this loading part in the GameLoop class and not in the ColorObjectManager class? I thought it would help if I passed the Context to the constructor of ColorObjectManager, but it didn't! I guess I'm missing some knowledge here where I can create objects and not. Can I get some help to sort this out? Thanks!
getResource needs a Context object. If you pass the context to ColorObjectManager you can retrieve resources with context.getResources()
I agree with the answer posted by blackbelt. Pass the activity context to the constructor of ColorObjectManager from your activity class.
new ColorObjectManger(ActivityName.this);
Constructor
Context mContext;
public ColorObjectManager(Context context)
{
this.mContext= context;
}
Then use the context to get resources.
Edit:
If you want to use the context only in your load method
public ColorObjectManager(Context context)
{
load(context);
}
To get access getResources() , Activity context is required. Your ColorObjectManager is not an Activity. So you need to pass the Activity context to this class.

Categories

Resources