Preserving ListView on subsequent activity launches - android

The main activity has a "To ListView" button that launches a new activity with a list view. Each time this new activity loads, it calls an AsyncTask() method that retrieves some JSON remotely, parses it, and binds the data to the list view. Then, setContentView() is called (in onPostExecute()) to show the UI. How do I preserve the list view data, or at least the data array (for rebinding) on subsequent launches of that activity so that the ASyncTask() doesn't have to be called every time?
The ASyncTask() should get called only at the beginning (and not until when the application is forcefully terminated), subsequent calls should be done manually by the user perhaps with an onClick() event. I have tried setting a boolean for that purpose, but if so how to display the previous state of the list view when the boolean is false? I have also looked into onResume() and onBackPressed() but they don't seem to be much relevant.
Main.java:
toListView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(getApplicationContext(), ListView.class);
startActivity(myIntent);
}
});
ListView.java:
public class ListView extends ActionBarActivity {
private static boolean isFirstLaunch = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isFirstLaunch) {
// execute the ASyncTask();
isFirstLaunch = false;
}
// else display previous listview data or last activity state
}// onCreate()

For persisting data in your Android application, you have two obvious choices.
Using an SQLite database. This alternative is good if you have quite a lot of data to manage, sort and maintain. E.g if your JSON response contains a lot of data, persisting it to a database would be a good solution. Then you would simply query the database instead of executing the AsyncTask repeatedly. Vogella provides an excellent tutorial on this matter.
Using SharedPerefences. This alternative is better if you only have a couple of variables to relate to. Storing the data in SharedPreferences relieves you of the work needed to design and implement database support for you application. See the official documentation for a reference.

Related

Pass reference of one Activity to another Activity

I know that I can pass some values between Activities using intent.
However, if I want to pass whole Activity to another Activity I think it is not good approach.
Is there another way to do that?
I have Settings Activity in which I am changing some Colors. So after I come back to my Main Activity I would like to apply those colors. To do this, I need access to MainActivity fields after I change Color value, so inside PreferenceActivity. In other words, I want to have access to Main activity fields from PreferenceActivity class. Any ideas?
You should be using a SharedPreference and then accessing that in your main activity. I recommend you reading up at http://developer.android.com/guide/topics/ui/settings.html because it seems like you are implementing your settings activity incorrectly. The part you might be specifically interested in is the "Read Preferences" section. However, I strongly suggest you read through the whole thing and then implement your settings the proper way.
Updated answer with the 3 different ways (that I can think of):
1) Start your preference activity using startActivityForResult(), then in your onActivityResult() access the SharedPreference and make your necessary changes. See here
2) Register a SharedPreferenceChangeListener with your MainActivity, which will be called when any changes happen to your SharedPreference. See here for a detailed discussion. Also see my initial response.
3) In your MainActivity's onResume(), access the SharedPreference and then make your changes there. I do not like this method because you will be cluttering onResume() with more logic and you will also probably have to have a variable that keeps track of the state of the variable you are interested in.
I would personally go with option 2 because the callback was created for this exact purpose.
I think you could pass the value by using method putExtra(name, value).
And after you start new activity you can get the value you pass before by using method getStringExtra(name).
Shared preferences can be used. If you want your changes to be reflected right away add listener. Refer to SharedPreferences.onSharedPreferenceChangeListener. Its an easy way to do.
If you want to lots of changes required in many activity from you change in any one.
And access last modify data from all Activity and modify also.
for example.
Constants.java
public class Constants
{
public static String name;
}
In your MainActivity you have an editText.
MainActivity.java
public class MainActivity extends Activity {
private EditText yourName;
private Button btn;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
yourName = (EditText) findViewById(R.id.yourName);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
Constants.name = yourname.getText().toString();
Intent intent = new Intent(getApplicationContext(),Activity2.class);
startActivity(intent);
}
});
}
In your Activity2 you have an TextView and that getting value which you enter in MainActivity.java without pass in Intent.
Activity2.java
public class Activity2 extends Activity {
private TextView yourName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
yourName = (TextView) findViewById(R.id.tv_yourName);
// directly use ferom serializable class
yourname.setText(Constants.name);
}
like that you use many values from all activity and modify from all activity.

show Progress Dialog within a CursorLoader in a fragment android

Good day, as the title say, anyone know how to implement a progress dialog while loading data from a CursorLoader within a fragment. can't find any example in this regard. Any link or guide on how to do it will be highly appreciated. Thank you
I think #Michal's solution would be good for showing an indeterminate ProgressDialog via ProgressDialog#setIndeterminate(true) so I've added a +1. I'm not sure adding a Fragment to a Fragment like this (SomeFragment adding DialogFragment..) is approved as I've come a cropper on SO before suggesting something similar. Plus, it's ok that the ProgressDialog is used here since ultimately it is a component of the fragment thus belongs under the fragment without needing to exist as a separate Fragment entity.
To expand on this answer, if you were wanting to provide a real-time progress update then I would suggest that after each "unit of work" (the lowest denominator of work you can count at the CursorLoader level) you should fire an event via the LocalBroadcastManger (keep it local, no one else needs to know) which your Fragment will be listening for.
On receiving the event under the Fragment's nested BroadcastReceiver#onReceive() method can get a reference to your Fragment's ProgressDialog and increment the displayed progress with incrementProgressBy(diff) or similar.
If however, you just want to pop-up a "I'm doing something" dialog then setting the ProgressDialog to use setIndeterminate(true) will suffice.
Finally, have you considered using the pattern of adding the indeterminate progress dialog to the ActionBar? That is how a lot of the core apps operate whilst an underlying CursorLoader is working. It would be nice to keep it consistent. Check out items pertaining to requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS).
Cheers.
Update
To achieve the last approach you need to set-up your parent activity (the bit owning the ActionBar) with code similar to (I'm just writing this from memory!);
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Note that this is requested before you have called setContentView...
getWindow().requestFeature(Window.FEATURE_PROGRESS);
setContentView(R.layout.yourLayout);
At this point you have said, "I would like that spinny progress thing in my Activity ActionBar". Now depending on your activity implementation you can choose to show the indeterminate progress bar immediately in onCreate by writing;
setProgressBarIndeterminateVisibility(true);
But this may be too simplistic if an additional action causes the CursorLoader to be started. What is important to note throughout this exercise is that the progress dialog in the ActionBar is a feature of the owning activity and not your underlying Fragment. You don't want your fragment assuming that the INDETERMINATE_PROGRESS feature has been requested since (a) it may not have and (b) it's not it's prerogative to understand such things. In other words if you find yourself writing getActivity().setProgressBarIndeterminateVisibility(true) stop and think.
I think you should leverage a more decoupled approach where the underlying Fragments says, "I have started to perform a load" so in your CursorLoader callback, onCreateLoader something like;
#Override
public Loader<Result> onCreateLoader(int id, Bundle b) {
// Fire event saying this load is starting.
final Intent loadStarted = new Intent();
loadStarted.setAction(YourFragment.LOAD_STARTED);
return new SomeCursorLoader(this.getActivity());
}
Your activity can be listening for this event and then when it receives it, sets the indeterminate progress bar visibility to true.
Similiarly, when the CursorLoader callback, onLoaderFinished is called then fire another event like;
#Override
public void onLoadFinished(Loader<Result> loader, Result data) {
// Fire event saying this load is finished.
final Intent loadFinished = new Intent();
loadFinished.setAction(YourFragment.LOAD_FINISHED);
}
Finally your activity can then sets the indeterminate progress bar visibility to false onReceivieing this event and the cursor results are shown to the user...
I think you can implement LoaderCallback in your Fragment there you can use
callback methods like onCreateLoader to show dialog, and onLoadFinished to dismiss dialog. Look at code below:
public class SomeFragment extends Fragment implements LoaderCallbacks<Result> {
private DialogFragment dialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//you should implement yours DialogFragment
dialog = new DialogFragment();
//Start loader
getLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<Result> onCreateLoader(int id, Bundle b) {
//Show dialog
dialog.show(getFragmentManager(), "TAG");
return new SomeCursorLoader(this.getActivity());
}
#Override
public void onLoadFinished(Loader<Result> loader, Result data) {
handler.sendEmptyMessage(0);
}
#Override
public void onLoaderReset(Loader<Result> arg0) {
}
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
dialog.dismiss();
}
};
}

Why does my ListView managed by a custom CursorAdapter refreshes automatically with new data?

I have had this "issue" for sometime on my app and it always bothered me that I didn't understand it's behavior, but never cared about asking why, until now.
I have a ListView (implemented through a ListActivity) with a custom layout for each list item. With that I have a custom CursorAdapter to properly fill all the list item elements (overriding newView and bindView, the ViewHolder pattern is already guaranteed when doing it like this, refer to: https://codereview.stackexchange.com/q/1057 if needed).
This is the main Activity and there are 2 actions that can update the content of the ListView, create a new item or edit a current one. For both, the same activity is used (which adapts itself to the type of action), something like this:
startActivityForResult(intent, ACTIVITY_NOTE_EDITOR_CREATE);
startActivityForResult(intent, ACTIVITY_NOTE_EDITOR_EDIT);
What actually distinguishes the action type is the intent contents, the requestCode is ignored in the sub activity.
Now, the documentation has this to say about the method above:
When this activity exits, your onActivityResult() method will be
called with the given requestCode.
But I'm not using onActivityResult. Actually, I'm not doing anything when the sub activity exists (either in create or edit mode). And the behavior I've noticed so far is this:
Create: Almost all the time I return to the main activity, the ListView is automatically updated with the new item. However, sometimes, rarely though, the ListView is not populated with the new item. I need to destroy the main activity (exiting the app for instance) and recreate it and then the new item will be there.
Edit: I never noticed similar behavior as just described for the create action. So far, the ListView was always automatically updated with the new content changes.
My question is, what's happening behind the scenes that makes my ListView to automatically update it's contents when I never call cursor.requery() nor manually notify of any changes to the content? And why the hell doesn't it work all the time?
Should I maybe force a cursor.requery() myself on onActivityResult?
public class MainActivity extends ListActivity {
private static final int ACTIVITY_NOTE_EDITOR_CREATE = 0;
private NotificationHelper mNotificationHelper;
private AgendaNotesAdapter mAgendaAdapter;
private Cursor mAllNotesCursor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNotificationHelper = Program.getNotificationHelper();
mAgendaAdapter = new AgendaNotesAdapter(this);
mAgendaAdapter.open();
initializeNotesListActivity();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent;
switch(item.getItemId()) {
case R.id.actionbar_item_new_note:
intent = NoteEditorMainActivity.createIntent(this);
startActivityForResult(intent, ACTIVITY_NOTE_EDITOR_CREATE);
return true;
}
return false;
}
private void initializeNotesListActivity() {
mAllNotesCursor = mAgendaAdapter.fetchAllNotes();
startManagingCursor(mAllNotesCursor);
NotesListAdapter notesAdapter = new NotesListAdapter(this, mAllNotesCursor,
mNotificationHelper, mAgendaAdapter);
setListAdapter(notesAdapter);
}
}
P.S: I know about CursorLoader and it's something in the roadmap, but I don't have time to deal with such code rewrites for the time being. Just thought I'd let you know. For now, I just need to know what is happening with my current code.
Your call to startManagingCursor() means that when the activity is stopped, the Cursor is deactivated, and when the activity is restarted, the Cursor is requeried.
Should I maybe force a cursor.requery() myself on onActivityResult?
This should automatically occur for your managed Cursor.

android - pass data from one activity to a list in a second activity

new to droid programming. im having a small problem that im sure is simply fixed but ive done some searching and a bunch of tutorials but cant seem to find just what i need so i figured id ask. My app has 2 activites, the first activity is just a simple form where a user enters course information(class title, professor..etc.)
the first activity passes the data which is supposed to be stored in a list in the second activity. problem is that only the first course gets stored in the list, after the first time nothing new gets added to the second activity. Can someone point me in the right direction please? thanks in advance
First Activity
public class CourseDetail extends Activity {
//Course c = new Course();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button save=(Button)findViewById(R.id.save);
save.setOnClickListener(onSave);
}
private View.OnClickListener onSave=new View.OnClickListener() {
public void onClick(View v) {
EditText course=(EditText)findViewById(R.id.course);
EditText professor=(EditText)findViewById(R.id.professor);
EditText location=(EditText)findViewById(R.id.location);
EditText officehrs=(EditText)findViewById(R.id.officehrs);
Intent i=new Intent(CourseDetail.this, CourseList.class);
i.putExtra("myCourse", course.getText().toString());
i.putExtra("myProfessor", professor.getText().toString());
i.putExtra("myLocation", location.getText().toString());
i.putExtra("myOfficehrs", officehrs.getText().toString());
startActivity(i);
}
};
}
Second Activity
public class CourseList extends Activity {
Button btnCourse;
List<Course> model = new ArrayList<Course>();
CourseAdapter adapter=null;
private String dCourse="";
private String dProfessor="";
private String dLocation="";
private String dOfficehrs="";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.clist);
ListView list =(ListView)findViewById(R.id.courses);
adapter=new CourseAdapter();
list.setAdapter(adapter);
Course c = new Course();
Bundle extras = getIntent().getExtras();
dCourse = extras !=null ? extras.getString("myCourse") :"no value entered";
dProfessor = extras !=null ? extras.getString("myProfessor") :"no value entered";
dLocation = extras !=null ? extras.getString("myLocation") :"no value entered";
dOfficehrs = extras !=null ? extras.getString("myOfficehrs") :"no value entered";
c.setCourse(dCourse);
c.setProfessor(dProfessor);
c.setLocation(dLocation);
c.setOfficeHrs(dOfficehrs);
btnCourse =(Button)findViewById(R.id.btnCourse);
btnCourse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
finish();
}
});
}
You are just getting the user entered value in CourseDetail activity and displaying the received value inside the CourseList activity, that means you are not storing these values permanently.
Go through this Android - Data Storage document.
When you move to 2nd activity i.e. CourseList activity, at that time fetch the data from the SQLite table and display the same. whenever you get new values from previous activity, at that time just update the list by adding the new data in ArrayList and make a call on adapter.notifyDataSetChanged()
Some suggestions:
Have your CourseList extend ListActivity instead of just Activity - check out some tutorials on that which should help you set things up correctly.
There seems to be a bit of confusion with how you're handling your lists - you have your model variable but don't seem to be doing anything with it. Again, have a look at a ListView tutorial (just google "android listview tutorial").
You seem to have figured out that you can use "intents" to pass information from one activity to another, but since you're only doing this in the onCreate() method, it's only happening once. Try doing this in your ListActivity's adapter once for each item.
Don't give up on Android, keep trying :-)
Some suggestion:
You have to add your object to the adapter: adapter.add(c); after you get the data.
Call adapter.notifyDataSetChanged() to notify the system that your data for the listView has been changed. Call list.invalidate() to refresh it.
I noticed that you set the button with the finish() method. Hmm, if you do so, the next time you get to CourseList Activity from CourseDetail, the adapter will be null again. No previously received data will be available. Is this what you really want?
The problem is you are not adding the newly added items to the List.So before setting adapter you have to add all your objects like
list.add(c);

Android AsyncTask context behavior

I've been working with AsyncTasks in Android and I am dealing with an issue.
Take a simple example, an Activity with one AsyncTask. The task on the background does not do anything spectacular, it just sleeps for 8 seconds.
At the end of the AsyncTask in the onPostExecute() method I am just setting a button visibility status to View.VISIBLE, only to verify my results.
Now, this works great until the user decides to change his phones orientation while the AsyncTask is working (within the 8 second sleep window).
I understand the Android activity life cycle and I know the activity gets destroyed and recreated.
This is where the problem comes in. The AsyncTask is referring to a button and apparently holds a reference to the context that started the AsyncTask in the first place.
I would expect, that this old context (since the user caused an orientation change) to either become null and the AsyncTask to throw an NPE for the reference to the button it is trying to make visible.
Instead, no NPE is thrown, the AsyncTask thinks that the button reference is not null, sets it to visible. The result? Nothing is happening on the screen!
Update: I have tackled this by keeping a WeakReference to the activity and switching when a configuration change happens. This is cumbersome.
Here's the code:
public class Main extends Activity {
private Button mButton = null;
private Button mTestButton = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mButton = (Button) findViewById(R.id.btnStart);
mButton.setOnClickListener(new OnClickListener () {
#Override
public void onClick(View v) {
new taskDoSomething().execute(0l);
}
});
mTestButton = (Button) findViewById(R.id.btnTest);
}
private class TaskDoSomething extends AsyncTask<Long, Integer, Integer>
{
#Override
protected Integer doInBackground(Long... params) {
Log.i("LOGGER", "Starting...");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 0;
}
#Override
protected void onPostExecute(Integer result) {
Log.i("LOGGER", "...Done");
mTestButton.setVisibility(View.VISIBLE);
}
}
}
Try executing it and while the AsyncTask is working change your phones orientation.
AsyncTask is not designed to be reused once an Activity has been torn down and restarted. The internal Handler object becomes stale, just like you stated. In the Shelves example by Romain Guy, he simple cancels any currently running AsyncTask's and then restarts new ones post-orientation change.
It is possible to hand off your Thread to the new Activity, but it adds a lot of plumbing. There is no generally agreed on way to do this, but you can read about my method here : http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html
If you only need a context and won't use it for ui stuff you can simply pass the ApplicationContext to your AsyncTask.You often need the context for system resources, for example.
Don't try to update the UI from an AsyncTask and try to avoid handling configuration changes yourself as it can get messy. In order to update the UI you could register a Broadcast receiver and send a Broadcast.
You should also have the AsyncTask as a separate public class from the activity as mentioned above, it makes testing a lot easier. Unfortunately Android programming often reinforces bad practices and the official examples are not helping.
This is the type of thing that leads me to always prevent my Activity from being destroyed/recreated on orientation change.
To do so add this to your <Activity> tag in your manifest file:
android:configChanges="orientation|keyboardHidden"
And override onConfigurationChanged in your Activity class:
#Override
public void onConfigurationChanged(final Configuration newConfig)
{
// Ignore orientation change to keep activity from restarting
super.onConfigurationChanged(newConfig);
}
To avoid this you can use the answer givin here: https://stackoverflow.com/a/2124731/327011
But if you need to destroy the activity (different layouts for portrait and landscape) you can make the AsyncTask a public class (Read here why it shouldn't be private Android: AsyncTask recommendations: private class or public class?) and then create a method setActivity to set the reference to the current activity whenever it is destroyed/created.
You can see an example here: Android AsyncTask in external class

Categories

Resources