-1

I have a database of >100 objects. Each object has a specific image filename, subject string, title string and pdf filename.

I have an activity that is a grid view of objects.

When first created the grid view will load an array of objects to display. The exact content of the array can vary depending on the database query I run when first opening the activity.

The default setting is that all objects from the database are retrieved and shown. However I have times when only a subset of objects (based on subject) are retrieved and shown.

Clicking on a button in the grid opens an activity which shows the pdf of that object. Fine.

The issue is, when I 'go back' from the PDF activity, the grid view is recreated. Always with the 'default' all objects.

Thus if I have a grid view showing objects of only a certain 'subject', if I then open the pdf activity, upon returning the grid view is recreated and shows all objects, not the original grid view before the pdf activity was started.

Question: 1) How do I return to the grid view WITHOUT it being recreated?

Code:

public class SubjectListActivity extends AppCompatActivity {

String DB_NAME = "XXXXX.sqlite";
String TABLE_NAME = "XXXXX";
DataBaseHelper myDBHelper;


private GridView gridView;
private MyAdapter myAdapter;

private ArrayList<Subject> subjectsArrayList;
private Context context;
private String subjectAreas = ""; // this is sent to the activity from whichever activity opened it.  


protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_subject_list);

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
        subjectAreas = extras.getString("subjectAreas");
    }

    gridView = findViewById(R.id.gv);


    // Database
    AssetDatabaseOpenHelper assetDatabaseOpenHelper = new AssetDatabaseOpenHelper(this, DB_NAME);
    assetDatabaseOpenHelper.saveDatabase();
    myDBHelper = new DataBaseHelper(this, DB_NAME);

    // populates subjectsArray
    subjectsArrayList = populateSubjects();

    loadGrid();

    // listener
    gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

            String title = subjectsArrayList.get(position).getSubjectTitle();
            String iconStyle = subjectsArrayList.get(position).getSubjectIconStyle();
            String pdf = subjectsArrayList.get(position).getSubjectPDF();
            String ref = subjectsArrayList.get(position).getSubjectRefs();
            String linked = subjectsArrayList.get(position).getSubjectLinked();

            Intent intent =  new Intent(view.getContext(), SubjectActivity.class);

            intent.putExtra("title", title);
            intent.putExtra("icon", iconStyle);
            intent.putExtra("pdf", pdf);
            intent.putExtra("ref", ref);
            intent.putExtra("linked", linked);
            intent.putExtra("subjectAreas", subjectAreas);

            startActivityForResult(intent, 1);


        }
    });

}


private void loadGrid(){

    myAdapter = new MyAdapter(getApplicationContext(), subjectsArrayList);
    gridView.setAdapter(myAdapter);

}

// The SQL query that populates the subjectArray depends on the 'subjectAreas' string which is set when the activity first opens. The default is 'getAllSubjects'
private ArrayList<Subject>  populateSubjects() {

    Cursor res = myDBHelper.getAllSubjects(TABLE_NAME);

    if (subjectAreas.equals("emerg")){res = myDBHelper.getEmergSubjects(TABLE_NAME);}
    else if (subjectAreas.equals("all")){res = myDBHelper.getAllSubjects(TABLE_NAME);}
    else if (subjectAreas.equals("ax")){res = myDBHelper.getAxSubjects(TABLE_NAME);}
    else if (subjectAreas.equals("mx")){res = myDBHelper.getMxSubjects(TABLE_NAME);}
    else if (subjectAreas.equals("ref")){res = myDBHelper.getRefSubjects(TABLE_NAME);}
    else if (subjectAreas.equals("cal")){res = myDBHelper.getCalSubjects(TABLE_NAME);}
    else if (subjectAreas.equals("search")){res = myDBHelper.getSearchSubjects(TABLE_NAME, keywords);}


        ArrayList<Subject> list = new ArrayList<>();

            if (res.getCount() == 0) {

            } else {
                while (res.moveToNext()) {
                    Subject subject = new Subject();
                    subject.setSubjectID(res.getString(0));
                    subject.setSubjectTitle(res.getString(1));
                    subject.setSubjectIconStyle(res.getString(2));
                    subject.setSubjectPDF(res.getString(3));
                    subject.setSubjectKeywords(res.getString(4));
                    subject.setSubjectLinked(res.getString(5));
                    subject.setSubjectRefs(res.getString(6));
                    list.add(subject);
                }

            }

            return list;
        }


// Tried sending 'back' subjectAreas from PDF activity and re-populating the array and gridview - this doesn't seem to be called  
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 1) {
        if(resultCode == RESULT_OK) {
            subjectAreas = data.getStringExtra("subjectAreas");
            subjectsArrayList = populateSubjects();
            myAdapter = new MyAdapter(getApplicationContext(), subjectsArrayList);
            gridView.invalidateViews();
            gridView.setAdapter(myAdapter);
        }
    }
}

}

Thanks in advance

Gridview showing only 'Management' subject objects After going back from viewing pdf, the Grdiview now shows all subject objects, not just 'Management' ones

UPDATE: Using savedInstanceState to 'save' my subjectAreas string I've used the following:

    private String subjectAreas = "";
private static final String subjectAreasSaved = "";

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_subject_list);

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
        subjectAreas = extras.getString("subjectAreas");
        title = extras.getString("title");
        keywords = extras.getStringArrayList("keywords");
    }

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        // Restore value of members from saved state
        subjectAreas = savedInstanceState.getString(subjectAreasSaved);
    }

And:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putString(subjectAreasSaved, subjectAreas);
    super.onSaveInstanceState(savedInstanceState);
}

Is the onSaveINstanceState called automatically when the second activity is open-end or do I need to call this? At present nothing appears to be happening

Nicholas Farmer
  • 697
  • 6
  • 27
  • 1
    I recommend that you edit your question and provide a [mcve] of this activity, showing when and how it is setting up the grid, plus how it is starting the PDF-viewing activity. That code will be far more valuable for getting answers than are screenshots of what the activity output looks like. – CommonsWare Jun 18 '19 at 15:10
  • done, appreciate any help, further advice – Nicholas Farmer Jun 18 '19 at 15:24
  • 1
    If that is the complete activity class, I don't see why it would be recreated based purely on forward/back navigation. Did you perhaps enable "Don't keep activities" in your Developer Options in Settings? You are going to have problems with configuration changes, though, which is where `onSaveInstanceState()` or a `ViewModel` come into play. BTW, and FWIW, the approaches you are using here (e.g., `GridView`, database I/O in the activity) are kinda old. – CommonsWare Jun 18 '19 at 15:29
  • Haven't enabled 'Don't keep activities' as all testing is on emulator (don't actually have an android device at present(!)). Guess that potential 'users' could have this enabled so I need to code for that outcome? It appears that 'onSaveInstanceState' is what I will need (agreed?). Thanks for advice re: gridview, as I'm sure you can guess I'm not an android developer and simply trying to re-create my iOS app (which for various reasons uses sqlite). These are the first ventures into android and want to get a 'basic version' running, and will look at other methods once comfortable. Thanks – Nicholas Farmer Jun 18 '19 at 15:37
  • "I need to code for that outcome?" -- I would not worry about it. "It appears that 'onSaveInstanceState' is what I will need (agreed?)" -- you need to do something for configuration changes, but your description of your symptoms does not include a configuration change. With respect to the problem in your question, put a breakpoint in your `onCreate()` method somewhere, and see if that gets tripped when you return to this activity after viewing the PDF. If it does not, then the activity is not being recreated, and something else is going on. – CommonsWare Jun 18 '19 at 16:08
  • Thanks. Already done some debugging and onCreate is definitely being called when returning from pdf activity – Nicholas Farmer Jun 18 '19 at 16:11
  • I can't explain that behavior. My only guess is that there is some configuration change that I am missing in your scenario, in which case look into `onSaveInstanceState()` or a `ViewModel`. – CommonsWare Jun 18 '19 at 16:14
  • Would setting the parent activity in the manifest be an issue? I have the gridview activity set as parent of the pdf activity - creating the back button on the toolbar – Nicholas Farmer Jun 18 '19 at 17:08
  • I don't use that technique personally, so I cannot comment on it. You might try temporarily removing that and seeing what happens. – CommonsWare Jun 18 '19 at 17:23

2 Answers2

2

Recreation is up to the framework. If it decides it needs the resources, it will destroy the previous copy whether you want it to or not. You can't stop it.

What you can do is implement onSaveInstanceState and onRestoreInstanceState to save all the data you need to recreate from that exact state.

Gabe Sechan
  • 77,740
  • 9
  • 79
  • 113
  • Can you have 'variations' of what is saved? i.e.can I save a specific variable as a different value depending on what it was? – Nicholas Farmer Jun 18 '19 at 15:26
  • You just save data to a Bundle object. So yes, you can save different data depending on the state, you just need to be able to understand what you saved in restore. Its not uncommon (and actually good practice) to let individual parts of an Activity (like fragments) save their own state, and each possible fragment would be saving different data. Just be aware Bundles do have a maximum size, so you may want to store things like query params rather than the full results of a db query. Or combine this with serialization to disk if you must – Gabe Sechan Jun 18 '19 at 15:29
  • Thanks, very helpful. I will actually only be saving one string 'subjectAreas'. Please see edit to Amin question above – Nicholas Farmer Jun 18 '19 at 15:53
  • Yes, in the code above, you are checking the savedInstantState in the onCreate method, but you need to implement both onSaveInstanceState, where you populate that bundle with the subjectAreas, and onRestoreInstanceState which will retrieve the savedInstantState bundle and use the subjectAreas to restore the state of the grid. – Michael Dougan Jun 18 '19 at 16:10
  • Android does not recreate Activities unless screen is rotated. If the system is low on memory it kills the whole process. – Onik Jun 18 '19 at 18:00
  • @Onik This is incorrect. There's dozens of reasons Android kills activities and recreates them. One of which is activities back down in the stack, where it may kill them for resources and recreate them when the user returns via finish() calls (usually through the back button). In fact the OS can be quite aggressive about this, it rarely keeps more than 1 or 2 activities in the back stack before killing them for resources. This is why the "Always kill Activites" setting exists- to emulate this occurring and allow you to test it on higher memory devices – Gabe Sechan Jun 18 '19 at 18:02
  • @Onik There are also several dozen other configuration changes other than rotation that cause recreation, although rotation is the most common. Other examples: changing the locale, sliding out a sliding hardware keyboard, changing the size of the screen (possibly via screen cast), etc – Gabe Sechan Jun 18 '19 at 18:04
  • _"One of which is activities back down in the stack..."_ I'd appreciate some references on that. Meanwhile, I prefer to believe Dianne Hackborn whose blogpost is referred [here](https://commonsware.com/blog/2011/10/03/activities-not-destroyed-to-free-heap-space.html) – Onik Jun 18 '19 at 18:23
  • @Onik Just launch a dozen activities in a chain (preferably on a low end device) and you'll see it. It happens all the freaking time, especially on older/low end devices. Its a problem I deal with literally every day. Please notice that blog post is 8 years old. Things have hanged since then. – Gabe Sechan Jun 18 '19 at 18:25
  • Agree, the post is old. I should test it and, if it's confirmed, reconsider my thinking). I'm sure we aren't talking about "Don't keep Activities" flag, right? – Onik Jun 18 '19 at 18:39
  • Yes. Sorry for getting the name wrong, but you got the gist – Gabe Sechan Jun 18 '19 at 18:40
0

Firstly, kudos should go to the authors who have greatly helped.

The issue with my code is beautifully explained, and solved with this thread: Stackoverflow thread

Basically the 'home' button in the Actionbar leads to the previous activity being recreated. Hence my issue. When I tried to implement InstanceState I kept getting a 'null' for savedInstanceState...which then lead me to the thread I linked above.

I really hope this helps others

Nicholas Farmer
  • 697
  • 6
  • 27