Starting a fragment calls activity's onCreate() - android

I have an AppCompatPreference SettingsActivity with a PreferenceFragment, like this:
public class SettingsActivity extends AppCompatPreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "activity onCreate called");
setupActionBar();
String userString = getIntent().getStringExtra(LoginActivity.USER);
Log.v(TAG, "UserString: " + userString);
...
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "GeneralPreferenceFragment onCreate called");
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
}
}
}
When I start the app, a LoginActivity authenticates with a server and passes user data (userString) to the SettingsActivity. It then starts a service with that data.
Everything is peachy and the service starts with no problem.
D/SettingsActivity: activity onCreate called
V/SettingsActivity: UserString: {some string of JSON user data}
But then I tap on General Preferences. As soon as I do so, this gets logged:
D/SettingsActivity: activity onCreate called
V/SettingsActivity: UserString: null
Because it logs activity onCreate called instead of GeneralPreferenceFragment onCreate called, it seems like the wrong onCreate() is being called. The app then crashes with a NullPointException trying to start the service with a null user.
I am trying to figure this out. Maybe the entire activity is restarting for some reason? Any suggestions on diagnosing this problem would help.

As your log shows, a new instance of activity is created.
This is the expected behaviour of the PreferenceActivity on a phone. Tablets use a two-pane layout and keep a single activity. But phones start a new activity.
AppCompat behaves the same.
You can however pass more data to the fragment with
public class MySettingsActivity extends PreferenceActivity {
#Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);
// You can build with xml settings that don't depend from UserString
loadHeadersFromResource(R.xml.preferences, target);
// For Settings that depend on UserString:
Header userHeader = new Header();
userHeader.title = ""; // TODO
user.fragment = UserFragment.class;
Bundle args = new Bundle(1);
// TODO Pass a User parcelable instead
args.putString(EXTRA_USER, userString);
userHeader.fragmentArguments = args;
}
}

Related

NullPointerException accessing activity from PreferenceFragment

Edit: Changing the language results in SettingsActivity.onCreate being called twice: (1) Due to recreate() and (2) due to context change.
However SettingsFragment.onCreate is being called only once, which kind of explains below question. But why isnt't SettingsFragment.onCreate being called after the second execution of the SettingsActivity.onCreate?
I am quite new to Android development and want to understand the reason which makes my application crash:
Within a settings activity I have placed a PreferenceFragment which allows the user to change the UI language. In order to take effect and not to spoil the back stack I call the recreate() method of the fragment's activity.
The first implementation looked like below:
(A)
public class SettingsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SettingsFragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit();
getFragmentManager().executePendingTransactions();
fragment.setActivity(this);
}
public static class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener
{
Activity m_activity;
public void setActivity(Activity activity){
m_activity = activity;
}
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
SharedPreferences preferences = getPreferenceScreen().getSharedPreferences();
preferences.registerOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
...
m_activity.recreate();
}
}
}
Sometimes above solution works, sometimes m_activity is Null and the application crahes. As a possible alternative solution I removed the 'setActivity' setter and called 'getActivity' within onSharedPreferenceChanged:
(B)
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
...
getActivity().recreate();
}
But this also results in sporadic NullPointerExceptions.
In scenario (B) I found that the fragment's 'onAttach' is not being called for any reason sometimes and hence 'getActivity' returns null. However I would expect scenario (A) not to crash since this isn't Null in onCreate?
Problem solved!
For those who have the same problem: I had to unregister the onSharedPreferenceChanged listener in SettingsFragment.onDestroy.

Force to launch SplashActivity onRestart another activity

I have an SplashActivity that create an ArrayList of custom CommerceObjects. This List will be used in the rest of the app, in diferent activities and fragments. The problem is that sometimes, when the app is stoped and then restarted, the objects List appear as not initialized. The solution is to check if the ArrayList is not null and in the case of being null force the SplashActivity launch again and remake the ArrayList. I tried to do this in the onRestart method in the rest of activities but is not working at all.
For example, this is the way that I'm checking in MainActivity if the ArrayList (created in SplashActivity) is null.
public class MainActivity extends AppCompatActivity {
...
#Override
protected void onRestart() {
// If the full list of commerces is null or is empty, launch the SplashActivity.
// Here check if the ArrayList of CommerceObjects is null
if (SplashActivity._commerces == null || SplashActivity._commerces.size() == 0) {
Intent mIntent = new Intent(MainActivity.this, SplashActivity.class);
startActivity(mIntent);
this.finish();
}
super.onRestart();
}
...
}
So, the array list to check is "_commerces". It's decalred as public static in SplashActivity. I need to check if is not null no mather what fragment or activity is currently in the front of the stack.
What I'm missing?
UPDATE
I recommend you to use onStart().
onRestart() is not called if App process is killed by Android OS.
https://developer.android.com/reference/android/app/Activity.html
ORIGINAL
The static variables will be initialized by Android OS.
see: static variable null when returning to the app
So I recommend you to avoid using static variables.
Make your Application class and Hold the CommerceObjects in your Application instance.
Following codes explains.
Make your Application class:
public class App extends Application {
private CommerceObjects mCommerces;
public void setCommerces(CommerceObjects commerces) {
mCommerces = commerces;
}
public CommerceObjects getCommerces() {
return mCommerces;
}
public static App get(Context context) {
return (App) context.getApplicationContext();
}
}
Set name of application in your AndroidManifest.xml:
<application
...
android:name=".App">
...
</application>
Initialize commerces in your SplashActivity:
public class SplashActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
initializeCommerces();
}
private void initializeCommerces() {
//do initialize tasks
...
CommerceObjects commerces = ...;
//set CommerceObjects to App
App.get(this).setCommerces(commerces);
//start other Activity. ex) MainActivity
}
}
Use commerces in anther Activity:
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//use CommerceObjects
CommerceObjects commerces = App.get(this).getCommerces();
...
}
}

Android return to app call method

I just recently started learning how to build android apps, and encountered a problem:
I want, when users leave the app (go to the homescreen, multitask), and they return, that the app calls a certain method. How can I do that?
This problem is more tricky than it may look like. When you return to app after leaving it, then is called method onResume of activity which was active when app was interrupted. But same happens when you go from one activity to another (onResume of second activity is called). If you just call method from onResume, it will be called every time onResume of any activity is called.
Take a look at this solution...
First, you have BaseActivity which is extended by all activities that need to call that method:
abstract public class BaseActivity extends Activity implements IName {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
protected void onResume() {
if (AppClass.getPausedActivity() != null) {
if (this.getClassName().equals(AppClass.getPausedActivity()))
//call specific method
}
AppClass.setPausedActivity("");
super.onResume();
}
#Override
protected void onPause() {
AppClass.setPausedActivity(this.getClassName());
super.onPause();
}
#Override
abstract public String getClassName();
}
As you can see it implements interface IName:
public interface IName
{
String getClassName();
}
BaseActivity in onPause (when it is interrupted) calls setPausedActivity method of AppClass which remembers last activity name that was interrupted. In onResume (when app and activity is continued) we compare name of current activity and last paused activity.
So, when app is interrupted, these names will be same because you paused one activity and you got back to the same one. When you call activity from some other activity these names will not be same and method will not be called.
Here is code for AppClass:
public class AppClass extends Application {
public static String pausedActivity;
public static String getPausedActivity() {
return pausedActivity;
}
public static void setPausedActivity(String _pausedActivity) {
pausedActivity = _pausedActivity;
}
}
Also, here is example of activity that extends BaseActivity:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
}
//here you set name of current activity
#Override
public String getClassName() {
return "MainActivity";
}
}
You are bound to the Activity lifecycle. You will need to implement corresponding logic to figure out if the user has been in your app before (i.e. using SharedPreferences).

setRetainInstance(true) does not persist member variables of the Fragment

I am using a View-less Fragment to store some data during orientation change of my Activity. It looks roughly like this:
public class BoardActivity extends BaseActivity {
private DataHandler mDataHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// initialize the data handler
mDataHandler = (DataHandler)mFragmentManager.findFragmentByTag("data");
if (mDataHandler == null) {
mDataHandler = new DataHandler();
mFragmentManager.beginTransaction().add(mDataHandler, "data").commit();
// initialize the data
mDataHandler.mThreads = ...;
} else {
// here, the data is taken and the ListView is filled again.
fillView();
}
}
public static class DataHandler extends Fragment {
private Topic[] mThreads;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
}
What happens is, that when the Activity is left (vor example with the home button) and for some reason is killed in the background, the app crashes upon restart of that Activity. The reason is that although the Fragment mDataHandler is found by the FragmentManager, its Member variable (mThreads) is null.
How come the Fragment itself can be retained but its variables are set to zero?
How come the Fragment itself can be retained but its variables are set to zero?
The fragment was not retained. Retained fragments are retained only for configuration changes. You did not go through a configuration change. Your process was terminated, because Android needed the RAM to support other apps.

Android Activity/PreferenceFragment lifecycle?

I have a very simple preferences setup in which a PreferenceFragment is added to an Activity. The Activity is also an OnSharedPreferenceChangeListener, since I want to update the summary of a particular preference whenever that preference is updated. Here's the Activity:
public class PrefsActivity extends Activity implements OnSharedPreferenceChangeListener {
private static final String PREF_KEY = "key goes here";
private PrefsFragment pf;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pf = new PrefsFragment();
getFragmentManager().beginTransaction().replace(android.R.id.content, pf).commit();
// pf.getPreferenceScreen() throws a NullPointerException here
}
#Override
protected void onPause() {
super.onPause();
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
}
#Override
protected void onResume() {
super.onResume();
updateSummary();
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(PREF_KEY)) {
updateSummary();
}
}
private void updateSummary() {
Preference p = pf.getPreferenceScreen().findPreference(PREF_KEY);
p.setSummary("Some string containing the updated value");
}
}
The PreferenceFragment is equally simple:
public class PrefsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
// At this point getPreferenceScreen() returns correctly
}
}
Here's the thing: Calling getPreferenceScreen() on the PreferenceFragment, immediately after it's been instantiated and added to the Activity in onCreate of PrefsActivity, throws a NullPointerException. It seems that getPreferenceScreen() starts returning a PreferenceScreen object in onCreate() of PrefsFragment, immediately after the call to addPreferencesFromResource() returns.
So my question is this: Since getPreferenceScreen() throws an NPE immediately after the PreferenceFragment has been added to the Activity, is the onCreate() of the PreferenceFragment called asynchronously/in a different thread? Otherwise I would have expected getPreferenceScreen() to return normally immediately after getFragmentManager().beginTransaction().replace(android.R.id.content, pf).commit().
is the onCreate() of the PreferenceFragment called asynchronously/in a
different thread?
No, it is run synchronously on the main thread. That means it has to wait until the Activitys onCreate exits before it gets to run. The FragmentManager schedules the Fragment callbacks, but they are not executed until the current callback (onCreate of the activity) is complete.
You have a few other places to access your PreferenceScreen. OnStart is called when the UI is ready, so the Fragment will be ready. onResume is called after onStart and is probably the best place to put something since it is invoked after coming back from a pause, too.

Categories

Resources