Problem using Application
I'm rewriting an app (first 'version' had little-to-nothing in terms of analysis and it ended up piling a bunch of problems I wanted to get rid of) and I'm bumping my head against a problem that never showed up in the first version.
Thing is: I have a Class for geographical data. It just supplies String arrays that I can tuck into spinners adapters. Since I used a values xml file, the class needs access to Context to get the proper resources.
Since I use this geographical data in several points of the app, I thought I could create a Class to extend Application and, in onCreate, instantiate the Geography Class, I thought it would be more efficient to load it just once and use it as many times as I wanted. This worked on my first version:
This is MyApplication class
private static Context context;
public void onCreate(){
super.onCreate();
MyApplication.context = getApplicationContext();
geografiaEspana = GeographyClass.getInstance(context);
}
public static GeographyClass getGeografiaEspana() {
if(ctx==null){
Log.w("TAPABOOK", "Tapabook.context nulo");
}
if (geografiaEspana==null){
Log.w("TAPABOOK", "Tapabook.geografiaEspana nula, instanciando");
geografiaEspana = GeographyClass.getInstance(ctx);
}
Log.i("TAPABOOK", "Tapabook.geografiaEspana instanciada");
return geografiaEspana;
}
And this is my GeographyClass
private static GeographyClass instance = null;
public static GeographySpain getInstance(Context context){
if(instance== null){
instance = new GeographySpain(context);
}
return instance;
}
public GeographySpain(Context context){
Resources res = context.getResources();
// load resources data
}
This worked, as I said, ok in my first version. However, in my new version I'm getting a NullPointerException on this line "Resources res = context.getResources();" I've checked and it turns out that the context I'm supplying it's null... And I don't get to understand why or what I'm doing wrong
Ok, I solved it (I'd swear I already commented on this, but since it's gone...).
Thing is, I'm not used to use Application classes and I had forgotten to declare MyApplication in the Manifest file. Noob mistake. As soon as I declared it, the app ran OK
Related
I follow Android N change language programmatically to changed language of my app in android N and above. However, I still have the problem with the application context instance.
In my Application class:
private static Application mInstance;
public static Context getApplication() {
return mInstance;
}
#Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
The language is changed, but Resources get from the Application context is not changed. For example:
MyApplication.getApplication().getResources().getString(stringId);
With return the wrong language string.
Can I update the application instance in this situation? I stuck to this problem for several hours. Because the MyApplication.getApplication() have used in many places throughout my app. So I can't convert to the Activity context.
Many thanks.
I have the same issue with one of my apps, because I do love my managers and utilities that doesn't require the context being passed every time.
My solution involves two separate contexts, one application context and one locale context. This doesn't solve all issues like inflating with correct locale using the correct style, for this you need to use the activity context. However, if you need to get the correct string or image from the resources based on the current locale, then this solution will work.
public class MainApplication extends Application {
private static Context applicationContext;
private static Context localeContext;
public static Context getAppContext() {
return applicationContext;
}
public static Context getLocaleContext() {
return localeContext;
}
#Override
public void onCreate() {
super.onCreate();
setTheme(R.style.AppTheme);
applicationContext = getApplicationContext();
updateLocaleContext();
}
public static void updateLocaleContext() {
localeContext = LocaleHelper.wrapContext(applicationContext);
}
}
The LocaleHelper.wrapContext should use a similar solution as the accepted answer on Android N change language programmatically and all activites need to implements attachBaseContext. Every time the language changes MainApplication.updateLocaleContext needs to be called. Note: the localeContext do not retain the style set in the onCreate function
Now you can use the MainApplication.getLocaleContext() for resources that depend on correct locale, while using MainApplication.getAppContext() for, e.g., inflating views that do not depend on the locale. Note: you could also place the localeContext in LocaleHelper to reduce the coupling
I (having mediocre developing skills) actually try to use Sugar as a database wrapper for my android project.
Therefore, I was following along the "Getting-Started-Guide" (http://satyan.github.io/sugar/getting-started.html) to get ready as soon as possible.
I created a class for my entities, called DataSet.java :
import com.orm.SugarRecord;
public class DataSet extends SugarRecord{
int someData;
double evenMoreData;
public DataSet(Context ctx){
super(ctx);
}
public DataSet(Context ctx,
int someData,
long evenMoreData) {
super(ctx);
this.someData = someData;
this.evenMoreData = evenMoreData;
}
}
I call the class in the following way:
someGreatClass something;
someMoreGreatCode somemore;
DataSet dataSet = new DataSet(
ctx, // Here Eclipse throws the error
something.method(),
somemore.anothermethod());
DataSet.save();
When I try to build this and to push it onto my device, Eclipse refuses to compile and throws this error:
ctx cannot be resolved to a variable
Considering the fact that I'm relatively new to Android development, the error may be obvious and I hope to get a tip how to solve this.
P.S.: Furthermore, I don't fully get the developer's statement in the getting-started-Note:
Please retain one constructor with Context argument. (This constraint will be removed in subsequent release.)
Thank you very much!
// Edit: Did edit the class name from LocationDataSet to Data set for clarification
First of all, the getting-started-note tells you that you need a constructor with only a context parameter, you did this here so that's ok
public DataSet(Context ctx){
super(ctx);
}
about
ctx cannot be resolved to a variable
I think you don't have a variable called ctx, I don't know if you're familiar with android context? (basically a context is a service or an activity), if you're using this code in an activity or a service, just use the 'this' keyword and not the ctx variable
The code you provide doesn't really show what you're doing, but you showed us the code from 'DataSet', but the error happens with a LocationDataSet? And you're calling save on DataSet?
The save method must be called on an object, not a class.
Also don't forget that sugar needs the special application class in the manifest
UPDATE with example:
Your dataset class (the sugarrecord) should look like this, that's ok in your code as far as I can see
public class DataSet extends SugarRecord<DataSet>{
private String someData;
public DataSet(Context c){
super(c);
}
public DataSet(Context c, String someData){
super(c);
this.someData = someData;
}
}
An activity that uses the record should look like this
public class SomeActivity extends Activity {
public void someMethodThatUsesDataSet(){
// Create a dataset object with some data you want the save and a context
// The context we use here is 'this', this is the current instance of SomeActivity,
// you absolutely need this, I think this is what you're doing wrong,
// you can't use ctx here because that's not a known variable at this point
DataSet example = new DataSet(this, "data you want to save");
// Tell Sugar to save this record in the database
example.save();
}
}
I am trying to use getString() to get an String from resources to assign it to an String array before my activity is created:
private static final String[] MenuNames = {
Resources.getSystem().getString(R.string.LCMeterMenu),
Resources.getSystem().getString(R.string.FrecMenu),
Resources.getSystem().getString(R.string.LogicAnalyzerMenu),
"Prueba con achartengine",
Resources.getSystem().getString(R.string.BrazoMenu)
};
When I use Resources.getSystem().getString(R.string.LCMeterMenu), Eclipse doesn't complain but I get an error at runtime:
Caused by: android.content.res.Resources$NotFoundException: String Resource ID #0x7f0a000a
But if I put inside onCreate():
Log.i("StringR", "String: " + getString(R.string.LCMeterMenu));
I get the String but I can't assign it to the final String I defined before. If I use only getString() before onCreate() I get and static error message. How can I use resources before onCreate() for global variables?
You cannot initialize a static final field from resources; the field needs to be initialized at the time the class is initialized and that happens before the application resources have been bound at run time. (By the way, the reason you cannot use Resources.getSystem() is that the Resources object you obtain that way contains only system resources, not any application resources.)
If you need those strings available before the application resources are bound, the only practical thing to do is to put the strings into the code directly. However, the "Android way" would be to organize your code so initialization only needs to happen during (or after) onCreate(). Just initialize the string array in onCreate() and don't worry about making the fields static or final.
If you don't want the string array to be associated with a particular activity, then you can subclass Application and read the array from resources inside the application class's onCreate() method. (You also need to declare your custom application class in the manifest.) However, the docs recommend against such an approach. (Since the array is private, I suspect that it is closely tied to a single activity anyway, so the use of an Application subclass doesn't seem warranted.)
An alternative is to declare a singleton class for your array. The singleton accessor function then needs a Context so it can retrieve the resources if necessary:
public class StringArray {
private static String[] theArray;
public static String[] getArray(Context context) {
if (theArray == null) {
theArray = context.getResources().getStringArray(R.array.my_strings);
}
return theArray;
}
}
(This assumes the string data are defined in a <string-array> resource like #JaiSoni suggested in his answer.) Once again, the member field cannot be declared final.
No, you can't use Resources before onCreate(). You can get the instance of Resources in onCreate() by using getResources() where you can get all the Strings. Also the strings are already declared as static by defining them in the strings.xml.
Pseudo code for accessing the Resources,
Resources res = getResources();
String app_name = res.getString(R.string.app_name);
Another approach could be to initialize the static array with resource identifiers (which are already available as opposed to the resources themselves).
private static final int[] MenuNames = {
R.string.LCMeterMenu,
R.string.FrecMenu,
...
};
This way, you can defer the loading of resources to when they are actually available:
String s = getResources().getString(MenuNames[i]);
The following is a working approach to initialize static final variables in android from XML, such as strings.xml.
Subclass application and provide a "static context"
Register the application class in manifest
Use the static context to initialize your constants
1. MyApplication.java
public abstract class MyApplication extends Application {
private static Context context;
#Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
/**
* Returns a "static" application context. Don't try to create dialogs on
* this, it's not gonna work!
*
* #return
*/
public static Context getContext() {
return context;
}
}
2. AndroidManifest.xml
<application
android:name=".android.application.MyApplication"
<!-- ... -->
</application>
3. Your application code, e.g. Activity
private static final String[] MenuNames = {
getContext().getString(R.string.LCMeterMenu),
getContext().getString(R.string.FrecMenu),
getContext().getString(R.string.LogicAnalyzerMenu),
"Prueba con achartengine",
getContext().getString(R.string.BrazoMenu)
};
protected static Context getContext() {
return MyApplication.getContext();
}
For working examples refer to AbstractApplication and PreferencesServiceSharedPreferences.
Note that this approach also has its downsides:
Apart from being opposed to the "Android way" (as #Ted Hopp suggested in his answer),
it makes testing a bit difficult. That is why the call to MyApplication.getContext() is wrapped in another method. As it is a static method, overriding it in testing code is not simple. But you could use a framework such as Powermock for this purpose.
In addition it is a bit prone to NullPointerExceptions. As soon as the context is null (e.g. in your testing code) the application code crashes. One option to overcome this, is to do the initialization in a constructor, where you could react to getContext()returning null (see example).
Whatever you get by the getString(int resId) will already be a constant for your application. Why do you have to keep it in another final static variable. You can read it like that whenever you want, right?
I have many shared preference for my app (mostly relating to color customization) and I'm unsure what the best method is to store/use them at runtime.
Currently I am doing something like this (with more or less preferences depending on the view) in every activity/fragment:
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
int buttonbg = settings.getInt("buttonmenu_bg", 0);
int buttontxt = settings.getInt("buttonmenu_txt", 0);
int headerclr = settings.getInt("header", 0);
And then using those to set the various colors in the display. This seems like a lot of overhead to have to call the PreferenceManager each time and go through all that.
So I started looking at creating an application class, reading the preferences in once and using static variables from the application class in the activities/fragment to set the display.
My question is, are there any drawbacks or gotchas to doing this that I should consider before I venture further down the Application class path?
If you are not using so many static variables so this may not affect your application.But the problem with static variable may arise when your app goes to background and the app running on front requires memory so it may clear your static data,so when you will go to your app you may find nothing (null) in place of static data.
The purpose of the Application class is to store global application state or data (in memory of course), so your approach is correct. I've used it multiple times and it works like a charm.
What I usually do is to create a Map member variable and provide methods for getting and putting values into it, looks like this:
package com.test;
...
...
public class MyApp extends Application{
private Map<String, Object> mData;
#Override
public void onCreate() {
super.onCreate();
mData = new HashMap<String, Object>();
}
public Object get(String key){
return mData.get(key);
}
public void put(String key,Object value){
mData.put(key, value);
}
}
Then from my activities, I just do ((MyApp) getApplication()).get("key") or ((MyApp) getApplication()).put("key",object). Also, don't forget to set the android:name attribute in your manifest file, under the application tag:
<application
...
...
android:name="com.test.MyApp">
</application>
Is there any particular reason why you are not setting the display colors in res/values/styles.xml?
I need to find a solution that holds and accesses large chunks of complex global data and methods. It has to be accessible from within activities and normal instance variables of various data classes.
This is how I have done it. I would just like to know if there is anything wrong with it or if there is a better/cleaner way.
First I extend Application like recommended many times...
public class MainDataManager extends Application{
public ... large chunks of data in arrays, lists, sets,....
//static variable for singleton access from within instance variables of other classes
public static MainDataManager mainDataManager;
//create and init the global data, and store it in the static variable of the class
#Override
public void onCreate() {
super.onCreate();
//in case it should get called more than once for any reason
if (mainDataManager == null) {
init();
mainDataManager = this;
}
}
Now accessing it from within activities like everywhere recommended...
MainDataManager mainDataManager = (MainDataManager)getApplicationContext();
And since I need to access it from normal instances of data classes ...
public class MyDataClass {
public MainDataManager mainDataManager;
public String name;
public MyDataClass(String namex) {
this.name = namex;
//this is why I defined the static variable within MainDataManager, so
//one has access to it from within the instance of MyDataClass
this.mainDataManager = MainDataManager.mainDataManager;
}
public void examplesForAccessing() {
//some examples on how to access the global data structure and associated methods
mainDataManager.someMethodAccess();
xyz = mainDataManager.someDataAccess;
mainDataManager.someIndirectMethodAccess.clear();
mainDataManager.someOtherData = false;
}
}
Since I have not done this so far, I would like to know if there is anything wrong with this. Memory, efficiency, ...
Thanks very much!
May I add a little sidenote?
I could also have just used a class MainDataClass and access by MainDataClass.var or MainDataClass.method(). Is there any REAL disadvantage?
Is the data in both cases held in heap/stack?
You haven't given much detail about your "large chunks of data" but keep in mind that the onCreate method is the first things that runs when your application is starting and it runs on the main/UI thread. This means that if you do long tasks in your init() method your UX will be poor, not to mention that you are risking an ANR exception.
The solution for that is simple:
Keep your onCreate short
Create a BG thread and use it to run all initialization code
Show a "Splash"/"Welcome" screen with the a proper progressbar while the BG thread is running.