The Android would create your Activity again to load the proper configuration including layout. The lifecycle methods(onCreate - onStart - onResume) will be called again and you can get data onCreate() if you have saved any data onSavedInstance(). However, your AsyncTask lost pipeline to your Activity and the code of executing the AsyncTask will be called again. This is not definitely what we want.
Let's have a look at code how to avoid it:I'm going to create a simple app to display weather using Open Weather API for demo. We need one Activity, one Fragment for UI ListView first.
MainActivity.java
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new MainFragment())
.commit();
}
}
}
AppCompatActivity is new class introduced begining AppCompat library v21.1.1. ActionBar Activity has been deprecated.
MainFragment.java
public class MainFragment extends Fragment {
private ProgressBar mProgressBar;
private ListView mListView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
mProgressBar = (ProgressBar)rootView.findViewById(R.id.progressBar);
mListView = (ListView)rootView.findViewById(android.R.id.list);
return rootView;
}
}
res/layout/fragment_main.xml
It's just normal implementation so far. From now, it's important part. Let's create AsyncTask. But it should not lost the pipe line to the MainActivity. How can we achieve this?
How about we use a Fragment without UI? If then, the Fragment will not be affected by device rotation. Fortunately you can use a Fragment without layout. We can create a dummy Fragment and make it retained calling setRetained(true). This can only be used with fragments not in the back stack like our case. The fragment lifecycle will be slightly different when an activity is recreated, which means onDestory() will not be called but onDetach() will be still called. And then we put the AsyncTask inside of the dummy Fragment.
public class TaskFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
private class WeatherTask extends AsyncTask {
@Override
protected ArrayList doInBackground(String... params) {
String cityName = params[0];
return WeatherFetcher.getForecastJson(cityName);
}
}
}
WeatherFetcher.getForecastJson is to get JSON data using Open Weather API and parse to return an ArrayList of weather forecast.
Now, we have to communicate MainFragment to display forecast list. You can use an interface to communicate between Activity and Fragment or Fragments. We can define an Interface inside of DummyFragment for this:
public class TaskFragment extends Fragment {
......
public static interface Callback {
public void onReady(TaskFragment fragment);
public void onPreExecute();
public void onProgressUpdate(Integer... values);
public void onPostExecute(ArrayList forecasts);
public void onCancelled();
}
}
The interface has almost similar method to AsyncTask. Of course, you can use different prototype depending on your needs. Now, you have to implement DummyFragment.Callback in MainFragment to communicated with DummyFragment.
public class MainFragment extends Fragment implements TaskFragment.Callback{
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//if (savedInstanceState != null){
//Recover your data here
//mProgressBar.setProgress(savedInstanceState.getInt(KEY_PROGRESS));
//}
FragmentManager fm = getActivity().getSupportFragmentManager();
mTaskFragment= (TaskFragment)fm.findFragmentByTag(ASYNC_TASK);
if (mTaskFragment == null){
mTaskFragment = new TaskFragment();
mTaskFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mTaskFragment, ASYNC_TASK).commit();
}
if (!mTaskFragment.isRunning()){
mTaskFragment.start("Sydney");
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save your data here
//outState.putInt(KEY_PROGRESS, mProgressBar.getProgress());
}
@Override
public void onPreExecute() {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void onProgressUpdate(Integer... values) {
//Do something
}
@Override
public void onPostExecute(ArrayList forecasts) {
if (forecasts != null) {
if (mAdapter == null) {
mAdapter = new ForecastAdapter(getActivity(), forecasts);
mListView.setAdapter(mAdapter);
} else {
mAdapter.setItems(forecasts);
}
}
mProgressBar.setVisibility(View.GONE);
}
@Override
public void onCancelled() {
mProgressBar.setVisibility(View.GONE);
}
}
You have to call callback methods in the DummyFragment.
public class TaskFragment extends Fragment {
private boolean isRunning = false;
private Callback mCallback = null;
private WeatherTask mTask = null;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(getTargetFragment() instanceof Callback)) {
throw new IllegalStateException("Target fragment must implement the Callback interface.");
}
mCallback = (Callback)getTargetFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void start(String cityName){
if (!isRunning){
mTask = new WeatherTask();
mTask.execute(cityName);
}
}
public void cancel() {
if (mCallback != null) mCallback.onCancelled();
if (mTask != null) {
mTask.cancel(false);
mTask = null;
}
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
private class WeatherTask extends AsyncTask {
@Override
protected ArrayList doInBackground(String... params) {
if (!isCancelled()) {
String cityName = params[0];
return WeatherFetcher.getForecastJson(cityName);
}
return null;
}
@Override
protected void onPreExecute() {
if (mCallback!=null) mCallback.onPreExecute();
isRunning = true;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (mCallback!=null) mCallback.onProgressUpdate(values);
isRunning = true;
}
@Override
protected void onPostExecute(ArrayList forecasts) {
if (mCallback!=null) mCallback.onPostExecute(forecasts);
isRunning = false;
}
@Override
protected void onCancelled() {
if (mCallback!=null) mCallback.onCancelled();
isRunning = false;
}
}
}
Lifecycle of the fragments
Lifecylcle of the MainFragment and TaskFragment will be like below when a device is rotated.
MainFragment.onStop()You can view the source code on Github.
TaskFragment.onStop()
MainFragment.onDestroy()
MainFragment.onDetach()
TaskFragment.onDetach()
MainFragment.onAttach()
MainFragment.onCreate()
TaskFragment.onAttach()
MainFragment.onActivityCreated()
MainFragment.onStart()
TaskFragment.onStart()
MainFragment.onResume()
TaskFragment.onResume()
Comments
Post a Comment