1

I got this app that's got three activities.

  1. A splashscreenActivity (the launcher activity) that loads data from SharedPreferences. Then proceeds to MainActivity after a second and quits( finish() ) itself
  2. The MainActivity that takes care of the UI and makes the necassary changes to the variables and the AlarmManager
  3. A broadcast receiver that is used only by the AlarmManager. The BroadCastReceiver sends notifications based on static values from the Splash.

All was going fine, until I added Material Design into the dependencies and update all my layouts and all that stuff. I'm not sure if that's what caused the bug. But now, my app fires two instances of my Splash Screen. It's not all that evident because, it disappears. But After I am done with my MainActivity and press the back button, the MainActivity returns to ANOTHER MainActivity. How could that happen ?

here are the codes :

SplashScreen.java >

package com.example.breathe;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

public class SplashScreen extends AppCompatActivity {

    public static boolean breatheToggled = false, persistence = true;
    public static int interval = 0;
    public static boolean TESTING = false;
    public static String BreathePreferencesKey="THE_KEY_TO_THE_VALLEY_OF_ETERNAL_PREFERENCES",
            breatheToggleString = "Isthetoggleswitchonoroff",
            breatheIntervalString = "IntervalForBreatheReminder",
            breathePersistenceString = "StringForPersistenceSwitchKey";
    public static void log(String x){ Log.d("jitin", x); }
    public static void trace(String x){ Log.d("trace", x); }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        trace("Splash Destroyed");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
//        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

        //LOAD DATA FROM PREFERENCES
        SharedPreferences preferences = getSharedPreferences(BreathePreferencesKey, 0);
        breatheToggled = preferences.getBoolean(breatheToggleString, breatheToggled);
        interval = preferences.getInt(breatheIntervalString, interval);
        persistence = preferences.getBoolean(breathePersistenceString, persistence);
        log("Read Preferences :"+
                "\nMainSwitch : "+String.valueOf(breatheToggled) +
                "\nInterval   : "+String.valueOf(interval) +
                "\nPersistance: "+String.valueOf(persistence) +
                "\nAutoCancel : "+String.valueOf(!persistence)
        );

        trace("Splash created, going to Main in 1 sec");
        //GO TO MAIN ACTIVITY AFTER DELAY
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                goToMain();
            }
        }, 1000);
    }

    //    public void goToMain(View view){ goToMain();}
    private void goToMain() {
        startActivity((new Intent(SplashScreen.this, MainActivity.class))
                //ALL LOADED DATA TO BE PASSED TO MAIN CLASS AS INTENT
                .putExtra(breatheToggleString, breatheToggled)
                .putExtra(breatheIntervalString, interval)
                .putExtra(breathePersistenceString, persistence)
        );
        finish();
    }
}

MainActivity.java >

package com.example.breathe;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.ToggleButton;

import static com.example.breathe.SplashScreen.TESTING;
import static com.example.breathe.SplashScreen.log;
import static com.example.breathe.SplashScreen.trace;

public class MainActivity extends AppCompatActivity {

    public static boolean toggleState, persistence;
    private int interval;
    private SeekBar intervalBar;
    private ToggleButton toggleButton;
    private TextView intervalText;
    private Switch persistenceSwitch;
    //Intent to BroadCast liSTENER
    PendingIntent calltoBroadcastListener;
    //ALARM MANAGER
    AlarmManager AM;

    @Override
    protected void onDestroy() {
        super.onDestroy();
        trace("Main destroyed");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
        toggleButton = findViewById(R.id.toggleButton);
        intervalText = findViewById(R.id.intervalValue);
        intervalBar = findViewById(R.id.intervalBar);
        persistenceSwitch = findViewById(R.id.persistenceSwitch);

        trace("Main created");
        //OBTAINING DATA FROM INTENT, WHICH COMES FROM SHAREDPREFERENCES LOADED IN THE SPLASH ACTIVITY
        Intent intentData = getIntent();
        toggleState = intentData.getBooleanExtra(SplashScreen.breatheToggleString, SplashScreen.breatheToggled);
        toggleBreatheSwitch(toggleState);
        interval = intentData.getIntExtra(SplashScreen.breatheIntervalString, SplashScreen.interval);
        setInterval();
        intervalBar.setOnSeekBarChangeListener(seekBarChangeListener);
        persistence = intentData.getBooleanExtra(SplashScreen.breathePersistenceString, SplashScreen.persistence);
        persistenceSwitch.setChecked(!persistence);
        persistenceSwitch.setOnCheckedChangeListener(switchChangeListener);

        calltoBroadcastListener = PendingIntent.getBroadcast(
                this,
                1,
                new Intent(this, BreathBroadcastReceiver.class),
                PendingIntent.FLAG_UPDATE_CURRENT
        );
        AM = (AlarmManager) getSystemService(ALARM_SERVICE);
    }

    Switch.OnCheckedChangeListener switchChangeListener = new Switch.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
            if (compoundButton.getId() == R.id.persistenceSwitch) {
                persistence = !b;

                //set state to Shared Preferences
                SharedPreferences.Editor preferences = getSharedPreferences(SplashScreen.BreathePreferencesKey, 0).edit();
                preferences.putBoolean(SplashScreen.breathePersistenceString, persistence);
                preferences.apply();

                log("Saved preference persistence : " + persistence + ", AutoCancel : " + !persistence);
            }
        }
    };

    SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
        @SuppressLint("SetTextI18n")
        @Override
        public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
            switch (seekBar.getId()){
                case R.id.intervalBar:
                    setInterval(i);
                    vibrate(30);
                default:
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {}

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            toggleBreatheSwitch(false);
            log("Saved Prefereces : interval : "+interval);
            SharedPreferences.Editor preferences = getSharedPreferences(SplashScreen.BreathePreferencesKey, 0).edit();
            preferences.putInt(SplashScreen.breatheIntervalString, (seekBar.getProgress()+1)*15);
            preferences.apply();
        }
    };

    @SuppressLint("ShortAlarm")
    private void setAlarm(){
        if (AM != null) {
            if (!TESTING)AM.setInexactRepeating(
                    AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    0,
                    1000*60*interval,
                    calltoBroadcastListener
            );
            else AM.setRepeating(
                    AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, 1000*60*1, calltoBroadcastListener);
            log("Alarm Set");
        } else log("Alarm is NULL");
    }

    private void toggleBreatheSwitch(boolean thetoggleState){
        toggleState = thetoggleState;

        //set toggle state to shared preferences;
        toggleButton.setChecked(toggleState);
        SharedPreferences.Editor preferences = getSharedPreferences(SplashScreen.BreathePreferencesKey, 0).edit();
        preferences.putBoolean(SplashScreen.breatheToggleString, toggleState);
        preferences.apply();
        log("Saved preferences : MainSwitch : "+ toggleState);

        //set changes to UI
        if (toggleState){ //REMINDER ON
            toggleButton.setTextColor(getResources().getColor(R.color.colorAccent));
            setAlarm();
        } else { //REMINDER OFF
            toggleButton.setTextColor(getResources().getColor(R.color.colorWhite));
            if (AM != null) AM.cancel(calltoBroadcastListener);
            log("Alarms Revoked");
        }
        vibrate(50);
    }

    public void toggleBreatheSwitch(View view) {
        //dummy function to handle calls from UI
        toggleBreatheSwitch(((ToggleButton)view).isChecked());
    }
    private void vibrate(int mSecs){
        try{ //To avoid vibrating when device is in total silence
            if (((AudioManager)getSystemService(AUDIO_SERVICE)).getRingerMode() == AudioManager.RINGER_MODE_SILENT) return;
        } catch (Exception ignore){}

        Vibrator vibe = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        if ((vibe != null) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O))
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
                vibe.vibrate(VibrationEffect.createOneShot(mSecs, VibrationEffect.EFFECT_TICK));
            else
                vibe.vibrate(mSecs);
    }

    private void setInterval(){
        //Function to update UI - set intervalText and intervalBar values
        setInterval(interval/15 - 1);
        intervalBar.setProgress(interval/15 - 1);
    }
    @SuppressLint("SetTextI18n")
    private void setInterval(int seekbarValue){
        //Function to update UI - set the intervalText
        int hours, minutes;
        interval = (seekbarValue+1)*15;
        hours = interval/60;
        minutes = interval%60;
        if (hours != 0 ) intervalText.setText(hours + "Hr ");
        if (minutes != 0)
            if (hours != 0)
                intervalText.setText(intervalText.getText().toString() + minutes +"min");
            else intervalText.setText(minutes +"min");
    }
}

BreathBroadcastReceiver.java >

package com.example.breathe;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;

import androidx.core.app.NotificationCompat;

import static com.example.breathe.SplashScreen.log;
import static com.example.breathe.SplashScreen.trace;

public class BreathBroadcastReceiver extends BroadcastReceiver {


    @Override
    public void onReceive(Context context, Intent intent) {
        trace("Broadcast Receiver received, sending notification");
        sendBreatheNotification(context);
    }

    public void sendBreatheNotification(Context context) {
        String NotificationChannelID = "Channel001";
        NotificationManager notifier = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notifier != null) {
            NotificationChannel chan = new NotificationChannel (
                    NotificationChannelID,
                    "Primary Breathe Reminders",
                    NotificationManager.IMPORTANCE_DEFAULT);
            chan.setSound(
                    Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.m),
                    new AudioAttributes.Builder()
                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                            .setUsage(AudioAttributes.USAGE_ALARM)
                            .build()
            );
            notifier.createNotificationChannel(chan);
        }

        if (notifier != null){
            NotificationCompat.Builder nBuilder = new NotificationCompat.Builder(context, NotificationChannelID)
                    .setAutoCancel(true)
                    .setSmallIcon(R.drawable.ic_lungs)
                    .setContentTitle("Breathe")
                    .setContentText("Don't Forget to Breath Deep.")
                    .setContentInfo("SOME INFO");

//                .setSubText("some sub text as well")
            if (!MainActivity.persistence) nBuilder.setTimeoutAfter(1000*10);

            notifier.notify(1, nBuilder.build());
            log("Breath Notification sent");
        } else log("Notifier null, Notification not displayed");
    }
}
manifest > 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.breathe">
    <uses-permission android:name="android.permission.VIBRATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity" />

        <activity android:name=".SplashScreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
            android:name=".BreathBroadcastReceiver"
            android:enabled="true"
            android:exported="true">

        </receiver>
    </application>

</manifest>

MainActivitylayout >

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="18dp"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatToggleButton
        android:id="@+id/toggleButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:checked="true"
        android:onClick="toggleBreatheSwitch"
        android:padding="24dp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.appcompat.widget.AppCompatSeekBar
        android:id="@+id/intervalBar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:max="15"
        android:progress="9"
        app:layout_constraintBottom_toTopOf="@+id/toggleButton"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/intervalText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="@string/reminder_interval"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        app:layout_constraintBottom_toTopOf="@+id/intervalBar"
        app:layout_constraintStart_toStartOf="parent" />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/intervalValue"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        app:layout_constraintBaseline_toBaselineOf="@+id/intervalText"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/intervalText" />

    <Switch
        android:id="@+id/persistenceSwitch"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:checked="true"
        android:text="@string/persistence_text"
        android:textOff="disabled"
        android:textOn="enabled"
        app:layout_constraintBottom_toTopOf="@+id/intervalText"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/voiceText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="Notification Voice"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        app:layout_constraintBottom_toTopOf="@+id/radioGroup"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <RadioGroup
        android:id="@+id/radioGroup"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:orientation="vertical"
        android:gravity="fill"
        app:layout_constraintBottom_toTopOf="@+id/persistenceSwitch"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <com.google.android.material.radiobutton.MaterialRadioButton
            android:id="@+id/radioButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Male" />

        <com.google.android.material.radiobutton.MaterialRadioButton
            android:id="@+id/radioButton2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Female" />
    </RadioGroup>

</androidx.constraintlayout.widget.ConstraintLayout>
Jitin
  • 346
  • 3
  • 13

1 Answers1

1

You may be seeing this nasty, long-standing Android bug:

Re-launch of Activity on Home button, but...only the first time

To check, install your app on the device, then kill the app (Settings->Apps->App-> force quit), then start your app by tapping the icon on the HOME screen and see if it now behaves normally.

David Wasser
  • 93,459
  • 16
  • 209
  • 274