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;
    }
}
}
1

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

15 Comments

Hmm, I was interested in that what you offer- Loaders, but I have faced with one article( 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?
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)
Could I do it throughout IntentServic?. I have found one article( neilgoodman.net/2012/01/01/… ) 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).
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 Services. 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.
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.