So, my first major application is almost coded and I'm doing optimizations on my code. The app works fine, but I'm not sure about my way of passing the context to other classes. I don't want to do it the wrong way. I stumbled upon articles and questions here in Stackoverflow about contexts and which is the right way to pass it to non-activity classes. I read the documentation as well, but being a Finn makes complicated tech speak even harder to understand.
So, a simple question. Is my way of passing my main activity's context to other (helper) classes correct? If not, where can I read more about better practice on these situations.
For example:
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle sis){
super(sis);
new Helper(MyActivity.this).makeMyAppAwesome();
}
}
Helper.java
public class Helper {
Context context;
Helper(Context ctx){
this.context = ctx;
}
public void makeMyAppAwesome(){
makeBaconAndEggsWithMeltedCheese(context);
}
}
Is this OK? It would be nice if someone could provide an easy to read article with examples on this subject.
You can do that using ContextWrapper, as described here.
For example:
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
public void makeMyAppAwesome(){
makeBaconAndEggsWithMeltedCheese(this);
}
}
And call the non activity class like this from an Activity
new MyContextWrapper(this);
It is usually in your best interest to just pass the current context at the moment it is needed. Storing it in a member variable will likely lead to leaked memory, and start causing issues as you build out more Activities and Services in your app.
public void iNeedContext(Context context) {...
Also, in any class that has context, I'd recommend making a member variable for readability and searchability, rather than directly passing or (ClassName.)this. For example in MainActivity.java:
Context mContext = MainActivity.this;
Activity mActivity = MainActivity.this;
I have passed context like this which solved my problem:
public class Utils extends ContextWrapper {
private final Context context;
public Utils(Context context) {
super(context);
this.context = context;
}
public void mymethod(){}
}
super(context); with ContextWrapper helped to make getBaseContext() and getApplicationContext() valid and this.context = context; captured context in variable which I can use wherever needed in methods.
Maybe alternatively you can just opt for using a constructor with this.context = context; and replace all occurrences of getApplicationContext() and getBaseContext().
Well, an even better way is to pass context directly to the method if using only few from a class for avoiding memory leaks.
You could also create a static instance reference to your MainActivity initialized in the onCreate() method
public class MainActivity extends AppCompatActivity {
public static MainActivity mMainActivity;
#Override
private onCreate(Bundle savedInstanceState){
//...
mMainActivity = this;
}
}
and call the context like this:
MainActivity.mMainActivity;
or write a method getInstanceOf() if it's clearer and/or you prefer using an accessor
MainActivity.getInstanceOf();
This strategy might provide you with some flexibility if you decide later that you would like to call an instance method contained in your main activity like so:
MainActivity.mMainActivity.myInstanceMethod();
Just a suggestion. Criticism is welcome and encouraged.
Related
I have a class that's inputting some data into SharedPreferences.
private static Context context;
context = MainActivity.getContext();
sp = (SharedPreferences) context.getSharedPreferences("currentData", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor editor = sp.edit();
editor.putString("name", placeName);
editor.apply()
I set the context using a method in my MainActivity class:
public static Context getContext(){
return context;
}
However I keep getting a null object reference. Tried multiple solutions from stack overflow and can't overcome the issue.
Why is context returning null?
This is because MainActivity.getContext() is null try passing the context from MainActivity to your class.
public Context context;
public YourClass(Context context) {
this.context= context;
}
In MainActivity init it like this:-
YourClass yours = new YourClass(MainActivity.this);
And also avoid using static contexts it might cause memory leaks !!
Context is an abstract class whose implementation is provided by the
Android system
Context is provided to any Activity by the android system during runtime (Activity indirectly extends Context). You are trying to get Context from MainActivity class via static method, which will not work and will always return null:
context = MainActivity.getContext();
You should always get Context from an instance of Activity, not the class itself. You can do this easily by passing an instance of your current Activity to the constructor of your class. Then, you call getContext() on an INSTANCE of that Activity, not the Activity class itself.
Also, wanted to mention that your code is mostly anti-pattern. You should never store Context in static variables. I'd recommend you read more about Activity lifecycle in android and Context - these are fundamental knowledge.
You can get context statically throughout the application
please try below code:
In the Android Manifest file, declare the following.
<application android:name="com.xyz.MyApplication">
</application>
Use this class
public class MyApplication extends Application {
private static Context context;
public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
}
public static Context getAppContext() {
return MyApplication.context;
}
}
Now you can call MyApplication.getAppContext() to get your application context statically.
You are getting the context from a static method in the class, that mean that method is called before the class is actually initialized. If there is no actual instance of the activity or if the OS haven't provide with context to the activity, then is null. The Activity has access to the contexto but after the Android underlining management initialized it, the class won't have the context by it self because it is there, if you notice Activities are never instantiated using the constructor because Android does it for you.
If you want to use a static method to having a nice syntax then the static method should be inside the class that use the shared preferences and should be passed from the activity, during any method of the Activity life cycle or when the user interacts with the ui (those listeners are set on the Activity life cycle).
class MyPreferences {
static void save(String toSave, Contex context) {
//TODO your operation here
}
}
And your activity:
public class MainActivity extends AppCompatActivity {
//Below is pseudo code, be careful on doing this precisely in the activity
#Override
onCreate() {
//TODO call super and setContentView
MyPreferences.save("foo", this);
}
}
It seems your problem is you are trying to make the other class to use the Activity, but in Android is the Activity that uses other classes
I'm in a little bit of a dilemma and I hope you guys can help me with it.
As you can see I have an AsyncTask in which I have some code to save Bitmap objects as .jpg file to the gallery. In the AsyncTask I'm also using a Context, but as I understand using a context of an Activity in this inner class can cause memory leak, so I changed it to a WeakReference<Context> weakContext; so the garbage collector can collect it.
But by using the Application context that I get from the passed View from the constructor I should archive the same effect as the weak context reference
So is any better to use than the other in this case?
public class ViewToBitmap {
private View view;
private WeakReference<Context> weakContext;
public ViewToBitmap(#NonNull View view) {
this.view = view;
}
// This?
private WeakReference<Context> getContext() {
weakContext = new WeakReference<>(view.getContext());
return weakContext;
}
// Or This?
private Context getContext() {
return view.getContext().getApplicationContext();
}
private class AsyncSaveBitmap
extends AsyncTask<Void, Void, Void>
implements MediaScannerConnection.OnScanCompletedListener {
#Override
protected Void doInBackground(Void... params) {
//TODO: Save bitmaps to gallery
//CONTEXT IS USED HERE
getContext().get()
return null;
}
}
Since the View object has explicit references to Context which was used upon view's inflation, you are effectively keeping a "transitive" hard reference to Context in instances of ViewToBitmap by keeping a hard reference to View.
Also, since AsyncSaveBitmap is not static, instance of this class has implicit reference to the enclosing instance of ViewToBitmap.
The net result is that as long as AsyncSaveBitmap exists, there is a chain of hard references to Activity that will prevent GC of that Activity.
So, the answer is: neither approach is good enough.
The best approach would be to refactor the logic in such a way that no long running code has references to Context, Activity, View, etc.
The most straightforward way of achieving this is to use Observer design pattern or Publish-Subscribe design pattern - this way you could "unregister" in life-cycle methods (e.g. onStop()), thus removing the potentially dangerous reference and preventing memory leaks.
EDIT:
For library purposes, where you don't necessarily need a specific Context and application's Context will suffice, the following patterns can be used (depending on whether your library exposed as Singleton or not):
// Use this approach if clients will use your library as Singleton
private static Context sAppContext;
public static void init(Context context) {
sAppContext = context.getApplicationContext();
}
// Use this approach if clients will instantiate your library's object on each use
private final Context mAppContext;
public MyLibraryEntryClass(Context context) {
mAppContext = context.getApplicationContext();
}
I am really new to Android development. In my app, I perform different async task and needed to save the result from those task to the database which requires context. Inspired from this answer I started using context in all my classes as a member variable. This seemed like a good method, but now ALL my background task and other classes which deal with preference have context as a member variable.
Code example -
public class Task extends AsyncTask<Void, Void, String> {
Context context;
public Task(Context context){
super();
this.context = context;
}
protected String doInBackground() {
//myAsyncTask
..
}
..
protected void onPostExecute(String response) { //response from the request
DbHelper helper = new DbHelper(context);
//save to db
}
I have about 3 or more of such tasks running consecutively at times. So whenever I need to do a background task, I first have to initialize the async task with the context and then initialize the DbHelper using that context. I feel like I'm doing it all wrong. This doesn't seem like a good method to me now (may lead to huge memory leaks imo). Plus it feels like duplication and that I can have access to the context in a better way.
Is it recommended to use context like this? Or does it actually have a disadvantage and I'm better off using some other method? Something like
public class MyApplication extends Application {
private static Context context;
public void onCreate(){
super.onCreate();
MyApplication.context = getApplicationContext();
}
public static Context getAppContext() {
return MyApplication.context;
//use context as MyApplication.getAppContext();
}
}
Which one is better to use?
Neither is correct. Do NOT maintain a reference to Context anywhere, it gives memory leaks because then the VM cannot garbage-collect the Context. We also tried several solutions but all failed. There's no other solution than pass the Context whenever you need it (if you look at the Android API you will see that it also works this way, and it's for a reason).
I have an app on Android 4.0. It uses the PreferenceManager class to -- among other things -- let the user specify how many decimal places of a number to show.
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
Generally I have no problem getting the app context in order to access the Preference Manager. My problem is that I have a class (let's call it Record) that isn't subclassing anything that has the app context; it's just a storage class, but it does have a field "NumDecPlaces". Right now, when I instantiate the class from within my app I just pass in the user's #dec places preference. It would be nice if Record could access the Preference manager directly. I suppose I could always instantiate Record with a pointer to the context from which it was created, but that's a lot to remember ;-)
So right now Record subclasses nothing. Any recommendations on what I can do to it to allow it to see the app context?
Thanks!
You could pass the Context object in the constructor. So whenever you try to use that class it will ask you pass a Context object and then use that to get SharedPreferences
For eg.
public Record(Context context)
{
mContext = context;
mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext)
}
You can also extend a class with Application, which will be global to the whole application and you can set the context in that class as a member variable and that context will be global to the whole application
Eg. class A extends Application{......}
You can do #Apoorv's suggestion or you can create another class that specifically stores the application context.
public class ContextResolver {
private static Context context;
public static void setContext(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null");
} else if (context instanceof android.app.Activity) {
context = androidContext.getApplicationContext();
} else if (context instanceof android.content.Context) {
context = androidContext;
}
}
public Context getContext() {
return context;
}
}
Now you need to call setContext() in the first activity that will be launched once.
public class MyFirstActivity extends Activity {
public void onCreate() {
ContextResolver.setContext(this);
}
}
Now you can retrieve the Context from any part of your code. So in your Record class you can just do this:
mPreferences = PreferenceManager.getDefaultSharedPreferences(ContextResolver.getContext());
I'd like to get my string-array without extending Activity in my custom class. Is there a way to do this?
String[] foo_array = getResources().getStringArray(R.array.foo_array); will not work without extending Activity, so I need a work-around.
Pass the context to the constructor of custom class and use the same
new CustomClass(ActivityName.this);
Then
Context mContext;
public CustomClass(Context context)
{
mContext = context;
}
use the context
String[] foo_array = mContext.getResources().getStringArray(R.array.foo_array);
Also keep in mind
Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
http://android-developers.blogspot.in/2009/01/avoiding-memory-leaks.html
Also check this
android getResources() from non-Activity class
Edit:
Change this
public class CustomClass(Context context)
{
}
To
public class CustomClass
{
Context mContext;
public CustomClass(Context context) // constructor
{
mContext = context;
}
}
try this,
Context context=getApplicationContext();
String[] foo_array = context.getResources().getStringArray(R.array.foo_array);
And, do not use Activity Context as that is tied to the Activity life cycle.
Update,
getApplicationContext() is from Context class. That means any thing extended Context have this method. This also means you will be able to use this from service or from other resources.
But, if you custom class do not extend Activity/context, you have to pass Context as parameter to use getApplicationContext()
if you declare your activity like this
myMethod(Activity activity) //this is bad
Bud if it is like following,
myMethod(Context context) //this is ok
but from above declaration do not pass Activity or Service Context as they have own life cycle. instead you will use getApplicationContext()
You need pass the Activity context to the Custom class.
private Context context;
public CustomClass(Context context)
{
this.context=context;
}
if you use numberpicker and pass String from sring xml then use this
np_Basic_Hight.setMinValue(0);
np_Basic_Hight.setMaxValue(71);
np_Basic_Hight.setDisplayedValues(getContext().getResources().getStringArray(R.array.hieght));