0

I have a trouble with getting Activity(Nullpointerexception) after that I have rotate screen and received callback from AsyncTask to update my views of the fragment. If I wont change orientation then everything is OK(but not all the time, sometimes this bug appears)

My main activity:

public class MainActivity extends SherlockFragmentActivity  {


public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.pager_layout);

    fm = getSupportFragmentManager();
    fm.addOnBackStackChangedListener(this);

    session = new SessionManager(getApplicationContext());
    if (session.isAuthorizated()) {

        disableTabs();
        FragmentTransaction ft = fm.beginTransaction();

        if (session.termsAndConditions()) {
            ft.replace(android.R.id.content, new TermsAndConditionsFragment(), "terms-and-conditions").commit();
        } 
        }
    } else {
        enableTabs();

        mTabsAdapter = new TabsAdapter(this, mViewPager);
        mTabsAdapter.addTab(actionBar.newTab().setText("Log in"), LoginFragment.class, null);
        mTabsAdapter.addTab(actionBar.newTab().setText("Calculator"), CalculatorFragment.class, null);

    }
}

That`s my fragment:

public class TermsAndConditionsFragment extends SherlockFragment implements OnClickListener, OnTouchListener, OnEditorActionListener, ValueSelectedListener, AsyncUpdateViewsListener {
private static final String TAG = "TermsAndConditionsFragment";

private TermsAndConditionsManager termsAndConditionsM;

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

public void prepareData() {
    if (getSherlockActivity() == null)
        Log.d(TAG, "Activity is null");
    termsAndConditionsM = new TermsAndConditionsManager(getSherlockActivity().getApplicationContext());
    termsAndConditions = termsAndConditionsM.getTermsAndConditions();
    ...
    // some stuff
    ...
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    rootView = init(inflater, container);
    return rootView;
}

private View init(LayoutInflater inflater, ViewGroup container) {
    rootView = inflater.inflate(R.layout.fragment_terms_and_conditions, container, false);

    //bla bla bla

    return rootView;
}

public void updateTermsAndConditionsView() {
    //update views here
}

@Override
public void onClick(View v) {
    ft = fm.beginTransaction();
    switch (v.getId()) {
        case R.id.etHowMuch:
            d = NumberPaymentsPickerFragment.newInstance(getSherlockActivity(), Integer.valueOf(howMuch.replace("£", "")), 0);
            d.setValueSelectedListener(this);
            d.show(getFragmentManager(), Const.HOW_MUCH);
            break;
  }
}

@Override
public void onValueSelected() {
    Bundle args = new Bundle();

    ...

    ExecuteServerTaskBackground task = new ExecuteServerTaskBackground(getSherlockActivity());
    task.setAsyncUpdateViewsListener(this);
    task.action = ServerAPI.GET_TERMS_AND_CONDITIONS;
    task.args = args;
    task.execute();
}

@Override
public void onUpdateViews() {
    prepareData();
    updateTermsAndConditionsView();
}
}

My AsyncTask with callback:

public class ExecuteServerTaskBackground extends AsyncTask<Void, Void, Void> {

private static final String TAG = "ExecuteServerTaskBackground";

Activity mActivity;
Context mContext;
private AsyncUpdateViewsListener callback;

public ExecuteServerTaskBackground(Activity activity) {
    this.mActivity = activity;
    this.mContext = activity.getApplicationContext();
}

public void setAsyncUpdateViewsListener(AsyncUpdateViewsListener listener) {
    callback = listener;
}

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

    ServerAPI server = new ServerAPI(mContext);
    if (!args.isEmpty())
        msg = server.serverRequest(action, args);
    else
        msg = server.serverRequest(action, null);
    return null;
}

@Override
protected void onPostExecute(Void result) {
    super.onPostExecute(result);
    callback.onUpdateViews();
}
}

Why does it behave so? How can I get activity correctly if I change orientation.

EDIT: As I understand correctly nullpointer appears after orientation changed and asynctask executed due to wrong reference between asyctask and Activity. Recreated activity doesnt have this reference thats why when I receive callback I use wrong activity reference which isn`t exist anymore. But how can I save current activity reference?

EDIT: I have decided to try realize my task throughout Service and that`s what I have done. Activity:

public class MainFragment extends Fragment implements ServiceExecutorListener, OnClickListener {

private static final String TAG = MainFragment.class.getName();

Button btnSend, btnCheck;
TextView serviceStatus;
Intent intent;
Boolean bound = false;
ServiceConnection sConn;
RESTService service;
ProgressDialog pd = new ProgressDialog();

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
    intent = new Intent(getActivity(), RESTService.class);
    getActivity().startService(intent);
    sConn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.d(TAG, "MainFragment onServiceConnected");
            service = ((RESTService.MyBinder) binder).getService();
            service.registerListener(MainFragment.this);
            if (service.taskIsDone())
                serviceStatus.setText(service.getResult());
            bound = true;
        }

        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "MainFragment onServiceDisconnected");
            bound = false;
        }

    };
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.main_fragment, container, false);
    serviceStatus = (TextView) rootView.findViewById(R.id.tvServiceStatusValue);
    btnSend = (Button) rootView.findViewById(R.id.btnSend);
    btnCheck = (Button) rootView.findViewById(R.id.btnCheck);

    btnSend.setOnClickListener(this);
    btnCheck.setOnClickListener(this);
    return rootView;
}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btnSend:
            pd.show(getFragmentManager(), "ProgressDialog");
            service.run(7);
            service.run(2);
            service.run(4);
            break;
        case R.id.btnCheck:
            if (service != null)
                serviceStatus.setText(String.valueOf(service.taskIsDone()) + service.getTasksCount());
            break;
    }
}

@Override
public void onStart() {
    super.onStart();
    Log.d(TAG, "Bind service");
    getActivity().bindService(intent, sConn, 0);
}

@Override
public void onPause() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: Unbind service");
    if (!bound)
        return;
    getActivity().unbindService(sConn);
    service.unregisterListener(this);
    bound = false;
}

@Override
public void onComplete(String result) {
    Log.d(TAG, "Task Completed");
    pd.dismiss();
    serviceStatus.setText(result);
}
}

Dialog:

public class ProgressDialog extends DialogFragment implements OnClickListener {

final String TAG = ProgressDialog.class.getName();

public Dialog onCreateDialog(Bundle savedInstanceState) {
    setRetainInstance(true);
    AlertDialog.Builder adb = new AlertDialog.Builder(getActivity())
            .setTitle("Title!")
            .setPositiveButton(R.string.yes, this)
            .setNegativeButton(R.string.no, this)
            .setNeutralButton(R.string.maybe, this)
            .setCancelable(false)
            .setMessage(R.string.message_text)
            .setOnKeyListener(new OnKeyListener() {
                @Override
                public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                    return true;
                }
            });
    return adb.create();
}

public void onClick(DialogInterface dialog, int which) {
    int i = 0;
    switch (which) {
        case Dialog.BUTTON_POSITIVE:
            i = R.string.yes;
            break;
        case Dialog.BUTTON_NEGATIVE:
            i = R.string.no;
            break;
        case Dialog.BUTTON_NEUTRAL:
            i = R.string.maybe;
            break;
    }
    if (i > 0)
        Log.d(TAG, "Dialog 2: " + getResources().getString(i));
}

public void onDismiss(DialogInterface dialog) {
    Log.d(TAG, "Dialog 2: onDismiss");
    // Fix to avoid simple dialog dismiss in orientation change
    if ((getDialog() != null) && getRetainInstance())
        getDialog().setDismissMessage(null);

    super.onDestroyView();
}

public void onCancel(DialogInterface dialog) {
    super.onCancel(dialog);
    Log.d(TAG, "Dialog 2: onCancel");
}
}

Service:

public class RESTService extends Service {

final String TAG = RESTService.class.getName();

MyBinder binder = new MyBinder();
ArrayList<ServiceExecutorListener> listeners = new ArrayList<ServiceExecutorListener>();
Handler h = new Handler();
RequestManager mRequest;
ExecutorService es;
Object obj;
int time;
StringBuilder builder;
String result = null;

public void onCreate() {
    super.onCreate();
    Log.d(TAG, "RESTService onCreate");
    es = Executors.newFixedThreadPool(1);
    obj = new Object();
    builder = new StringBuilder();
}

public void run(int time) {
    RunRequest rr = new RunRequest(time);
    es.execute(rr);
}

class RunRequest implements Runnable {

    int time;

    public RunRequest(int time) {
        this.time = time;
        Log.d(TAG, "RunRequest create");
    }

    public void run() {
        Log.d(TAG, "RunRequest start, time = " + time);
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Log.d(TAG, "RunRequest obj = " + obj.getClass());
        } catch (NullPointerException e) {
            Log.d(TAG, "RunRequest error, null pointer");
        }
        builder.append("result " + time + ", ");
        result = builder.toString();
        sendCallback();
    }
}

private void sendCallback() {
    h.post(new Runnable() {
        @Override
        public void run() {
            for (ServiceExecutorListener listener : listeners)
                listener.onComplete();
        }
    });
}

public boolean taskIsDone() {
    if (result != null)
        return true;
    return false;
}

public String getResult() {
    return result;
}

public void registerListener(ServiceExecutorListener listener) {
    listeners.add(listener);
}

public void unregisterListener(ServiceExecutorListener listener) {
    listeners.remove(listener);
}

public IBinder onBind(Intent intent) {
    Log.d(TAG, "RESTService onBind");
    return binder;
}

public boolean onUnbind(Intent intent) {
    Log.d(TAG, "RESTService onUnbind");
    return true;
}

public class MyBinder extends Binder {
    public RESTService getService() {
        return RESTService.this;
    }
}
}
Viktor M.
  • 4,393
  • 9
  • 40
  • 71
  • possible duplicate of [Activity restart on rotation Android](http://stackoverflow.com/questions/456211/activity-restart-on-rotation-android) – nikib3ro Mar 28 '13 at 15:03

1 Answers1

2

As you mention in your edit, the current Activity is destroyed and recreated on orientation change.

But how can I save current activity reference?

You shouldn't. The previous Activity is no longer valid. This will not only cause NPEs but also memory leaks because the AsyncTask might hold the reference to old Activity, maybe forever.

Solution is to use Loaders.

Aswin Kumar
  • 5,158
  • 5
  • 34
  • 39
  • Hmm, I was interested in that what you offer- Loaders, but I have faced with one article( http://www.grokkingandroid.com/using-loaders-in-android/ - section "When not to use Loaders") where is wroten that it`s bad to use Loaders when you are executing something and want to finish it then reinitialize loaders - server requests in my case. Just thing is that I send server request in async and make some changes in UI. What should I use exactly - Loaders + Service(to execute requests correctly) or what? What is the best and easier way? – Viktor M. Mar 29 '13 at 10:46
  • 1
    Well if its too expensive to re-query the server, then maybe you should use a service. 1. When Activity starts, bind to the service 2. Register to service as a listener via some interface 3. When required, ask the service to query the server and store result 4. If the activity is still alive, it receives a callback from the interface. If not, when binding to the service again (upon recreation), check if the result has been fetched by the server (add a function for it in the binder) – Aswin Kumar Mar 29 '13 at 11:38
  • Could I do it throughout IntentServic?. I have found one article( http://neilgoodman.net/2012/01/01/modern-techniques-for-implementing-rest-clients-on-android-4-0-and-below-part-2/ ) where is described RestService via IntentService, already with interface. In general, it seems the same what you wrote beside the last point - if activity recreates I need to check that result is fetched or not in onBind method. Where can I check it in this case that I wont send request again(because it`s critical). – Viktor M. Mar 29 '13 at 12:13
  • I don't think you should use `IntentService` coz they die after they have done their job, and is not guaranteed to be kept alive, unlike `Service`s. What I'm trying to say is, if you use `IntentService`, it will probably die off after making the server query, and the next time you bind to it, you might be starting another instance of it. Better use `Service`. And the `interface` I'm talking about is ur own custom interface, like a callback interface. The Activity will implement this, and there will be, say an `onComplete()` method which will be called by the service when it completes. – Aswin Kumar Mar 29 '13 at 13:08
  • Thanks for explanations, but I didn`t understand why intentService is worse than just service. Concerning that who will die and who not. I`m pretty sure intentService will die everytime when job is done but what is problem? Service will alive forever while you destroy it manually or by system. In some sence, intentService, maybe, will be better(not sure, I`m too young for all kind of services, didn`t use any of them yet) because it bind and unbind automaically and wont eat resources when there is nothing to do. And API looks like a bit shorter and intuitive than Service. – Viktor M. Mar 29 '13 at 16:20
  • There is some difference between these two things - http://www.swarut.com/node/125. Just to know everybody. Read a lot of another recomendations and understand that service is realy powerful if you need multi-threading. From another side, it`s a bit complexity due to it`s necessary to open and close service constantly and realy can be worse for application performance if it isn`t used but running. I`ll research a bit more how to work with Services properly and will try to implement it. Multi-threading seems to be a good feature for client-server application. – Viktor M. Mar 29 '13 at 17:08
  • 1
    Why I said prefer `Service` over `IntentService` *in your case* is because you need the query result to be around even after (possibly multiple) recreation of your `Activity`. If you used an `IntentService`, the query would be completed and the service will die. Now if the recreated `Activity` wants to access the query result, it will need to run the `IntentService` again, i.e., the query again. Unless you are persisting the result from the first time, ofcourse. – Aswin Kumar Mar 31 '13 at 06:36
  • I have created example project where is using Service (you can dowload it here - http://rghost.net/44946567). It looks quite well. Could you check it - is it that what you are talking about or I missed something? In the logs I see quite correct behaviour - onCreate, binding, connection establishment, executing, unbind when exit from app or on pause. If click execute and exit, execution process will be continued but UI wont be updated due to listeners remove onPause. Only one interesting thing - why service continue to execute tasks in background when user leave app and onUnBind was called? – Viktor M. Apr 01 '13 at 12:05
  • I thought if onUnBInd is called, tasks will be terminated. – Viktor M. Apr 01 '13 at 12:07
  • To update the UI after `Activity` recreate do the following: store the result of your useful-operation in the service itself, and add a getter for it, say `getUsefulResult()`. As soon as you register the listener to the service, call the `taskIsDone()` function, if it returns positive, call `getUsefulResult()`. – Aswin Kumar Apr 01 '13 at 12:41
  • `bind` and `unbind` are just plug-in points to the service, it doesn't have much to do with the lifecycle of the service itself. Imagine the service to be like a wire carrying electricity, its always alive. When you need something useful from it, you plug-in (bind) to it, get things done, then plug-off (unbind). – Aswin Kumar Apr 01 '13 at 12:45
  • OK, I`ve edit my code, now it`s so as you said I think. BUt in the real project I think I`ll save my result id db or at least in shared preferences. Thank you very much. All of your posts were very useful for me. Now I have at least something picture about what is that and how to work with it. – Viktor M. Apr 01 '13 at 13:08
  • One more question, I`ve implemented progressDialog to my fragment where will be called service tasks and when I change screen orientation I get nullpointer. As I understand it`s again due to wrong reference or context. I thought it was possible to retain my dialog if I will set RetainInstance(true) but nothing changes. SO another way is onSaveInstance where will be, for example, boolean progressStatus. That`s will be work but, maybe, there is something better way to do it. I know about that it`s possible to set "orientation" attribute in manifest but that`s no way I think. HOw do you fix it? – Viktor M. Apr 01 '13 at 14:49
  • This is getting pretty long, I think you better ask as a separate question :-) – Aswin Kumar Apr 01 '13 at 17:50
  • I`ll be glad if you help me here too http://stackoverflow.com/questions/15758213/android-why-dialogfragment-return-nullpointer-on-orientation-change =) – Viktor M. Apr 02 '13 at 07:12