34

I'd like to implement an update checker in an application, and I obviously only need this to show up once when you start the application. If I do the call in the onCreate() or onStart() method, it'll be shown every time the activity is created and this is not a viable solution.

So my question is: Is there a way to do something, like check for updates, just once per application start / launch?

I'm sorry if it's a bit hard to understand, I'm having difficulties explaning myself on this one.

Michell Bak
  • 13,182
  • 11
  • 64
  • 121

10 Answers10

57

SharedPreferences seems like ugly solution to me. It's much more neat when you use application constructor for such purposes.

All you need is to use your own Application class, not default one.

public class MyApp extends Application {

    public MyApp() {
        // this method fires only once per application start. 
        // getApplicationContext returns null here

        Log.i("main", "Constructor fired");
    }

    @Override
    public void onCreate() {
        super.onCreate();    

        // this method fires once as well as constructor 
        // but also application has context here

        Log.i("main", "onCreate fired"); 
    }
}

Then you should register this class as your application class inside AndroidManifest.xml

<application android:label="@string/app_name" android:name=".MyApp"> <------- here
    <activity android:name="MyActivity"
              android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
</application>

You even can press Back button, so application go to background, and will not waste your processor resources, only memory resource, and then you can launch it again and constructor still not fire since application was not finished yet.

You can clear memory in Task Manager, so all applications will be closed and then relaunch your application to make sure that your initialization code fire again.

Vitalii Korsakov
  • 45,737
  • 20
  • 72
  • 90
  • 1
    +1 This is the real solution, SharedPreferences complicates everything and make the things messy! – Sergio Carneiro Mar 16 '13 at 00:50
  • This too is not real solution. When you perform "Clear Data" from application manager then constructor gets executed again.Code should run only when app is re installed or a new install not when Clear Data operation carried :'( – Rauf Sep 25 '13 at 12:54
  • @Rauf Sorry, can't test it right now, but doesn't "Clear Data" close application? – Vitalii Korsakov Sep 26 '13 at 10:22
  • There is very little difference between Clear Data and Uninstall..Clear Data brings app back to its state when it was freshly installed..ie it just removes all data associated with it..but i need to catch a new installation – Rauf Sep 26 '13 at 10:32
  • @Rauf This topic about application start, not application installation. – Vitalii Korsakov Sep 26 '13 at 12:22
  • @VitaliiKorsakov yeah..can you help me in my scenario? any trick ? please see my question: http://stackoverflow.com/questions/19005821/run-code-only-once-after-a-fresh-installation-shared-preference-is-not-a-solutio?noredirect=1#comment28078766_19005821 – Rauf Sep 27 '13 at 06:25
  • what if the code needed to be run just once is clearing the SharedPreferences? It can't be done here and gives a NullPointerException cause it needs the activity I guess. – Fatima Jul 21 '14 at 15:25
  • best one. SharedPreferences can be cleared for many reasons and it's not reliable. Thank you – makkasi Dec 05 '14 at 09:04
  • This will execute more than once if you use the android:process tag in your AndroidManifest.xml See https://stackoverflow.com/a/3951911/1549046 – AtomicBoolean Feb 01 '22 at 16:42
4

The shared preferences approach is messy, and the application class has no access to an activity.

Another alternative I've used is to have a retained fragment instance, and within that instance, a lot more stuff can be done especially if you need access to the main activity UI.

For this example, I've used asynctask within the retained fragment. My AsyncTask has callbacks to the parent activity. It is guaranteed to run only once per application because the fragment is never destroyed-recreated when the same activity is destroyed-recreated. It is a retained fragment.

public class StartupTaskFragment extends Fragment {

public interface Callbacks {
    void onPreExecute();
    void onProgressUpdate(int percent);
    void onCancelled();
    void onPostExecute();
}

public static final String TAG = "startup_task_fragment";
private Callbacks mCallbacks;
private StartupTask mTask;

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    mCallbacks = (Callbacks) activity;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setRetainInstance(true); // this keeps fragment in memory even if parent activity is destroyed

    mTask = new StartupTask();
    mTask.execute();
}

@Override
public void onDetach() {
    super.onDetach();
    mCallbacks = null;
}

private class StartupTask extends AsyncTask<Void, Integer, Void> {

    @Override
    protected void onPreExecute() {
        if (mCallbacks != null) {
            mCallbacks.onPreExecute();
        }
    }

    @Override
    protected Void doInBackground(Void... ignore) {

        // do stuff here

        return null;
    }

    @Override
    protected void onProgressUpdate(Integer... percent) {
        if (mCallbacks != null) {
            mCallbacks.onProgressUpdate(percent[0]);
        }
    }

    @Override
    protected void onCancelled() {
        if (mCallbacks != null) {
            mCallbacks.onCancelled();
        }
    }

    @Override
    protected void onPostExecute(Void ignore) {
        if (mCallbacks != null) {
            mCallbacks.onPostExecute();
        }
    }
}
}

Then, in main (or parent) activity where you want this startup task fragment to run once.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    FragmentManager fm = getFragmentManager();
    StartupTaskFragment st = (StartupTaskFragment) fm.findFragmentByTag(StartupTaskFragment.TAG);

    if(st == null) {
        fm.beginTransaction().add(mStartupTaskFragment = new StartupTaskFragment(), StartupTaskFragment.TAG).commit();
    }

    ...
}

Ideas for retained fragment came from here: http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html. I just figured out its other uses aside from config changes.

Poly Bug
  • 1,514
  • 1
  • 14
  • 27
  • This is very useful when your start up task needs more information than is available at the creation of the Application. In my case, I need the user to be logged in before I run my start up task, so this approach works better for me. – Michael Robinson Jan 31 '15 at 04:29
4

looks like you might have to do something like this

PackageInfo info = getPackageManager().getPackageInfo(PACKAGE_NAME, 0);

      int currentVersion = info.versionCode;
      this.versionName = info.versionName;
      SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
      int lastVersion = prefs.getInt("version_code", 0);
      if (currentVersion > lastVersion) {
        prefs.edit().putInt("version_code", currentVersion).commit();
       //  do the activity that u would like to do once here.
   }

You can do this every time, to check if the app has been upgraded, so it runs only once for app upgrade

Yashwanth Kumar
  • 28,931
  • 15
  • 65
  • 69
2

Yes you can do it Using SharedPrefernce concept of android. Just create a boolean flag and save it in SharedPrefernce and check its value in your onCreate() method .

Sujit
  • 10,512
  • 9
  • 40
  • 45
  • 2
    The only problem is that a SharedPreference is not cleared when you exit the application. Luckily, my application includes a splash screen, which could be used to reset that SharedPreference every time the application is started. Don't know why I didn't think of that before - thanks for the push in the right direction :-) – Michell Bak Sep 09 '11 at 11:25
  • So this is not one time per app upgrade but once per launch? – Anthony Graglia Sep 09 '11 at 11:28
  • yes you are absolutely right....one more thing you can do is that whenever you are starting your Activity where you have written the code for update checker , you can use the intent.setFlag(FLAG_ACTIVITY_REORDER_TO_FRONT) so that onCreate() will not be called again. – Sujit Sep 09 '11 at 11:34
  • 1
    check my answer , if you need one time for app upgrade. – Yashwanth Kumar Sep 09 '11 at 11:36
  • 1
    I basically just ended up using Intent extras with a boolean value from the splash screen to tell if it's launching (true if from splash screen, false from anything else). It works beautifully. – Michell Bak Sep 09 '11 at 11:45
  • This is NOT the right method to do this,because when you clear application data from the application manager then too it will take as a new run even though app didn;t get uninstalled – Rauf Sep 25 '13 at 11:25
  • This works well - it only takes a few lines of code to implement it, and the performance is fast enough to add it to one of the lifecycle methods. If you have a SQLite database in your app, you can also use the update function to detect and upgrade or downgrade: http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#onUpgrade(android.database.sqlite.SQLiteDatabase,%20int,%20int) – Coder Roadie Apr 02 '15 at 00:36
1
 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
  if (!prefs.getBoolean("onlyonce", false)) {
    // <---- run your one time code here


    // mark once runned.
    SharedPreferences.Editor editor = prefs.edit();
    editor.putBoolean("onlyonce", true);
    editor.commit();
  }
}
Makvin
  • 3,475
  • 27
  • 26
1

This continues on @Vitalii's answer.

After having setup the Application class, if access to the Activity is required, we can use the aptly named android library "Once" https://github.com/jonfinerty/Once.

In the Application class's onCreate method

Once.initialise(this)

In the Activity / Fragment class's onCreate / onViewCreated method.

val helloTag = "hello"
if (!Once.beenDone(Once.THIS_APP_SESSION, helloTag)) {
    //Do something that needs to be done only once
    Once.markDone(helloTag) //Mark it done
}
Michael Hathi
  • 1,933
  • 2
  • 17
  • 27
0
      try {

          boolean firstboot = getSharedPreferences("BOOT_PREF",MODE_PRIVATE)
    .getBoolean("firstboot", true);

                    if(firstboot){
                //place your code that will run single time                  
getSharedPreferences("BOOT_PREF",MODE_PRIVATE).edit().
    putBoolean("firstboot", false)
                        .commit();


                    }
Rahulkapil
  • 294
  • 4
  • 13
0

I just solved doing this myself, I reopen my main activity multiple times throughout the application's execution. While the constructor is a valid approach for some things it doesn't let you access the current Application context to write toasts among other things.

My solution was to create a simple 'firstRun' boolean set to true in the class of my MainActivity, from there I run the contents of the if statement then set it to true. Example:

public class MainActivity extends AppCompatActivity
{
     private static boolean firstRun = true;

     @Override
     protected void onCreate(Bundle savedInstanceState)
     {
         if(firstRun)
         {
              Toast.makeText(getApplicationContext(), "FIRST RUN", Toast.LENGTH_SHORT).show();
              //YOUR FIRST RUN CODE HERE
         }
         firstRun = false;

         super.onCreate(savedInstanceState);
         //THE REST OF YOUR CODE
}
  • 2
    This does not appear to work. The boolean property just gets re-initialised as true again whenever the application gets created. – Cloud Jan 26 '20 at 22:32
0

I do this the same way as described in the other answer. I just have a global variable in the first activity which matches the release number from the manifest. I increment it for every upgrade and when the check sees a higher number, it executes the one-time code.

If successful, it writes the new number to shared preferences so it wont do it again until the next upgrade.

Make sure you assign the default to -1 when you retrieve the version from shared preferences so that you error on the side of running the code again as opposed to not running it and not having your app update correctly.

Anthony Graglia
  • 5,355
  • 5
  • 46
  • 75
  • Because I think all solutions with Shared Preferences is ultimately messy, when you have application constructors for such purpose. I downvoted all answers with SharedPreferences in them, if it will make you feel a little bit better :) – Vitalii Korsakov Apr 08 '13 at 18:26
0

Use SharedPreference for this-

  1. If you are not restarting your launcher activity again once your app is active then in that case you case use it.

  2. Use this in a Splash screen if you are implementing it in the app.

  3. If you are not using any splash screen then you need to create a activity with no view set and on it's oncreate call you can do start updation and start your main activity.

you can use counter value or boolean for this.

Here is SharedPreference doc:

http://developer.android.com/reference/android/content/SharedPreferences.html

Vineet Shukla
  • 23,865
  • 10
  • 55
  • 63