Force to launch SplashActivity onRestart another activity - android

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();
...
}
}

Related

What's the proper way to initialize an Android application?

Problem: I need to run some code at every start before my app is ready to be used.
At first, I tried doing it in a dedicated activity.
AndroidManifest.xml
<activity android:name=".MainActivity" />
<activity android:name=".StarterActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
AppLoader.java
public class AppLoader {
private static Object someInstance;
public static void load(Runnable onCompleteCallback) {
try {
someInstance = new Object();
//potentially long operation to initialize the app
Thread.sleep(5000);
onCompleteCallback.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void checkInitialized() {
if (someInstance == null) {
throw new RuntimeException("Not initialized");
}
}
}
StarterActivity.java
public class StarterActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppLoader.load(() -> {
MainActivity.start(this);
finish();
});
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
public static void start(Context context) {
Intent starter = new Intent(context, MainActivity.class);
context.startActivity(starter);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppLoader.checkInitialized();
}
}
This works fine if the app is cold started via the launcher icon but crashes in all other cases. Simple way to reproduce the issue:
Go to developer settings on your device and set "Background process limit" to "No background process"
Open the app
Open some other app
Open the app again. Result: it crashes.
Here's an article describing a similar problem: Android process death — and the (big) implications for your app
Possible solutions:
Lazy loading/reactive approach. I try to use it as much as possible but there is always some code I need to run in a blocking way before user can interact with the app so this is not enough.
Putting all of that code in App.onCreate(). This would probably work for small apps but I've seen large apps that take 5-10 seconds to initialize, and I doubt they use onCreate() for that. Possible downsides: ANR and/or excessive startup time in Android Vitals?
Checking if the app is initialized in a BaseActivity, but that would require either blocking onCreate or managing lifecycle callbacks manually which doesn't sound like a good idea.
So, what's the proper way to run some code every time the app is launched?
Note: Normally StarterActivity would be a splash screen, AppLoader would be injected, etc, but I left that out for simplicity.
AndroidManifest.xml
<application
android:name=".AppLoader"
AppLoader.java
public class AppLoader extends Application {
private static Object someInstance;
#Override
public void onCreate() {
super.onCreate();
// DO YOUR STUFF
}
}
Update
- Use Handler with splash screen.
public class StarterActivity extends AppCompatActivity {
private Handler handler;
private Runnable myStuffRunnable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new Handler();
myStuffRunnable = new Runnable(){
public void run(){
// DO MY STUFF
MainActivity.start(this);
}
};
}
#Override
protected void onPause() {
handler.removeCallbacks(myStuffRunnable);
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
handler.post(myStuffRunnable);
}
#Override
protected void onDestroy() {
handler.removeCallbacks(myStuffRunnable);
super.onDestroy();
}
}
Your app is throwing the RuntimeException you set in AppLoader.checkInitialized() method, because your someInstance object is losing it's state when the app goes to background and gets killed by the system ('cause you have set your device to hold zero background threads). So, when you try to reopen the app, the system launches MainActivity directly (and not StarterActivity) because it is trying to restore it's previous state. But variables are not restored, not even static variables.
So, if you need the Object someInstance on your MainActivity, you should integrate it's instantiation into MainActivitie's lifecycle, overriding methods like onSavedInstanceState, onRestoreInstanceState, etc, to properly handle and reaload this object if your app gets killed by the system.
Take a look on this https://developer.android.com/guide/components/activities/activity-lifecycle
If anyone's interested, I ended up just redirecting the user to StarterActivity if needed to make sure the necessary code is executed at every start.
public abstract class BaseActivity extends AppCompatActivity {
private boolean isCreated;
#Override
protected final void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!appLoader.isLoaded()) {
StarterActivity.start(this);
finish();
return;
}
onCreateActivity(savedInstanceState);
isCreated = true;
}
protected void onCreateActivity(#Nullable Bundle savedInstanceState) {
}
#Override
protected final void onDestroy() {
super.onDestroy();
if (isCreated) {
onDestroyActivity();
}
}
protected void onDestroyActivity() {
}
}
All activities extend BaseActivity (except StarterActivity) and override onCreateActivity/onDestroyActivity instead of onCreate/onDestroy.

Starting a fragment calls activity's onCreate()

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;
}
}

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

Android: using getIntent() only within onCreate?

In Android (targeting APIs 14-16) I have a MainActivity and a NextActivity. There is no difficulty using intents to start NextActivity from within MainActivity if the getIntent() method is called inside the onCreate() block of NextActivity:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int data = 7;
...
Intent intent = new Intent(this, NextActivity.class);
intent.putExtra("data", data);
startActivity(intent);
}
}
public class NextActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final int data = this.getIntent().getIntExtra("data", 7);
...
}
...
}
However, since the field data is being used inside an anonymous ("inner") class in NextActivity, I am compelled to declare it final.
I'd prefer not to declare fields final, and I can usually avoid doing so if I declare them at the beginning of the class, before onCreate() begins. But for some reason, the app crashes when NextActivity starts if the getIntent() statement appears (without the final keyword) outside of onCreate().
Any idea why?
You can't getIntent() before onCreate() -- there's simply no Intent available at that point. I believe the same is true for anything that requires a Context.
Your anonymous inner class can still call getIntent(), however, so you don't need to declare this as a variable at all.
According to your question what i understand is u don't want to declare data as final in next activity..Then y cant u try for this./
public class NextActivity extends Activity {
int data=0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
data = this.getIntent().getIntExtra("data", 7);
...
}
...
}
Try this...

using onClickView to call another class

I want to use a button click to pass selection parameters to another class that will build a map screen using the passed parameters. I am focused on getting my button action working. I a using onCLickListener and onCLickView as follows
Class1:
public class Class1 extends Activity implements OnClickListener {
Class2 class2;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
..........
Button button = (Button)findViewById(R.id.btn_configup1);
button.setOnClickListener(this);
}
public void onClick(View v) {
Class2 class2 = new Class2();
//Save state.. selections and params and use bundle
//to pass into class2
class2.execMapBuild();
}
}
Class2:
public class Class2 extends MapActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.drawable.navup);
}
public void execMapBuild() {
finish(); //just in case we return.
Intent intent = new Intent(CLass2.this, Class2.class);
startActivity(intent);
}
I have everything working except the desired button action. I want the button click in Class1.onVlickView to call Class2.execMapBuild using the button click action. I have the button click capturing the action and calling the execMapBuild method on Class2. But I get a NullPointerException as it moves from startActivity(intent) into onCreate.
I have tried several other ways of nailing this down, but this seems the best and I seem close to figuring it out. I would really appreciate an explanation of what I may be missing.
Added code that was initially not copied in.
To expand on #Heiko Rupp's answer, if you want Class2 to display a map, it needs to extend something like Activity. As such, you can't just call it with a normal method. You need to register the Activity in your manifest and then call it using an Intent. Here is a sample of the kind of thing you should be doing:
public class Class1 extends Activity implements OnClickListener {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button button = (Button)findViewById(R.id.btn_configup1);
button.setOnClickListener(this);
}
public void onClick(View v) {
Intent intent = new Intent(Class1.this,Class2.class);
intent.putExtra("key","data");
...
startActivity(intent);
}
}
public class Class2 extends MapActivity {
String mData;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Bundle extras = getIntent().getExtras();
if (extras != null) {
mData = extras.getString("key");
...
}
...
}
}
Can I also suggest that you use more descriptive class names than Class1 and Class2.
Class2 is no activity, so the callbacks of an Activity will not be called by the system.
And if it were an Activity, you could not just call into it via new Class2(), as still the callbacks are not executed.
Try to clean this up and then start Class2 activity from Class1 with an Intent as you are doing within execMapBuild().

Categories

Resources