8

I am trying to implement callback between AsyncTask and Fragment but cannot find correct info how to do it. The issue is that all callback implementations are between activity and asynctask but I need between fragment and asynctask. Could someone give me small working example how to implement it without activity. My action structure: Fragment call DialogFragment -> choose something and send server request to async task -> async task process everything and update view and some variables. My main problem is that I call prepareData() only once in onCreate and when I walk between other fragment and returns come back I see old data. That is to say there is not enough to update only view in onPost of asynctask. It will be good to have callback which will update the whole variables.

public class TermsAndConditionsFragment extends SherlockFragment implements OnClickListener, OnTouchListener, OnItemClickListener, onTaskListener {
 @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    fm = getSherlockActivity().getSupportFragmentManager();


    prepareData();
}

public void prepareData() {
     termsAndConditionsM = new TermsAndConditionsManager(getSherlockActivity());
    termsAndConditions = termsAndConditionsM.getTermsAndConditions();

    if (termsAndConditions != null) {
                 int totalPayments = Integer.valueOf(termsAndConditions.get(ServerAPI.NO_OF_PAYMENTS));
        if (totalPayments > 0) {
            paymentsData = termsAndConditionsM.getpayments();

            if (paymentsData != null) {

                payments = new ArrayList<Payment>();

                for (int i = 1; i <= totalPayments; i++) {
                    paymentValues = new Payment();
                    paymentValues.setPaymentID(Integer.valueOf(paymentsData.get(ServerAPI.PAYMENT_NO + "_" + i)));
                    paymentValues.setPaymentDate(paymentsData.get(ServerAPI.PAYMENT_DATE + "_" + i));
                    paymentValues.setPaymentTotalAmount(paymentsData.get(ServerAPI.PAYMENT_TOTAL + "_" + i));
                    payments.add(paymentValues);
                }
            }
        }
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    rootView = init(inflater, container);

    if (payments != null || termsAndConditions != null)
        updateTermsAndConditionsView();

    return rootView;
}

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

    ...
    return rootView;
}

public void updateTermsAndConditionsView() {
    etHowMuch.setText("£" + termsAndConditions.get(ServerAPI.AMOUNT_OF_CREDIT));
    etForHowLong.setText(Helpers.ConvertDays2Date(Integer.valueOf(termsAndConditions.get(ServerAPI.TERM_OF_AGREEMENT_IN_DAYS))));

    PaymentAdapter adapter = new PaymentAdapter(getSherlockActivity(), R.layout.custom_loan_item, payments);
    lvPayments.setAdapter(adapter);

    tvNoOfPayments.setText(termsAndConditions.get(ServerAPI.NO_OF_PAYMENTS));
    tvFirstPayment.setText(termsAndConditions.get(ServerAPI.FIRST_PAYMENT_DATE));
    tvTotalRepayable.setText("£" + termsAndConditions.get(ServerAPI.TOTAL_REPAYABLE));
}

@Override
public void onClick(View v) {
    ft = fm.beginTransaction();

    howMuch = etHowMuch.getText().toString();
    forHowLong = etForHowLong.getText().toString();

    switch (v.getId()) {
        case R.id.etHowMuch:
            f = new NumberPaymentsPickerFragment();
            args = new Bundle();
            args.putInt(Const.HOW_MUCH, Integer.valueOf(howMuch.replace("£", "")));
            args.putDouble(ServerAPI.PAYMENT_STEP, Const.PAYMENT_STEP);
            args.putString(Const.STATE, ServerAPI.TERMS_AND_CONDITIONS);
            f.setArguments(args);
            f.setTargetFragment(this, DIALOG_FRAGMENT);
            f.show(getActivity().getSupportFragmentManager(), Const.HOW_MUCH);
            break;
        case R.id.etForHowLong:
            f = new NumberPaymentsPickerFragment();
            args = new Bundle();
            args.putInt(Const.FOR_HOW_LONG, Integer.valueOf(Helpers.ConvertDate2Days(forHowLong)));
            args.putDouble(ServerAPI.PAYMENT_STEP, Const.PAYMENT_STEP);
            args.putString(Const.STATE, ServerAPI.TERMS_AND_CONDITIONS);
            f.setArguments(args);
            f.setTargetFragment(this, DIALOG_FRAGMENT);
            f.show(getActivity().getSupportFragmentManager(), Const.FOR_HOW_LONG);
            break;
        case R.id.tvPersonalDetails:
            sfm.saveCurFragment(ServerAPI.PERSONAL_DETAILS, 0);
            ft.replace(android.R.id.content, new PersonalDetailsFragment(), ServerAPI.PERSONAL_DETAILS).addToBackStack(null).commit();
            break;
        case R.id.tvAgreementDetails:
            sfm.saveCurFragment(ServerAPI.AGREEMENT_DETAILS, 0);
            ft.replace(android.R.id.content, new AgreementDetailsFragment(), ServerAPI.AGREEMENT_DETAILS).addToBackStack(null).commit();
            break;
        case R.id.bApply:

            break;
    }

@Override
public void onUpdateData() {
    Log.d(TAG, "Update data");

}

}

DialogFragment:

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

    Bundle args = getArguments();

    ...
}

public Dialog onCreateDialog(Bundle savedInstanceState) {

    ...

        return createDialog(v, R.string.for_how_long, etHowMuch, etForHowLong, etPromotionCode);
    }
    return null;
}
private Dialog createDialog(View view, int titleResID, final EditText howMuchField, final EditText forHowLongField, final EditText promotionCodeField) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle(titleResID);
    builder.setView(view);
    builder.setPositiveButton(R.string.set, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int id) {

            doShowProgress();

        }

        private void doShowProgress() {


            ExecuteServerTaskBackground task = new
                    ExecuteServerTaskBackground(getActivity());
                task.action = ServerAPI.GET_TERMS_AND_CONDITIONS;
                onTaskListener listener = new onTaskListener() {

                @Override
                public void onUpdateData() {
                    Log.d(TAG, "Updaaate");

                }
            };
            task.setListener(listener);
            task.args = args;
            task.execute();
        }

    }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int id) {
            dialog.cancel();
        }
    });
    return builder.create();
}

AsyncTask:

    onTaskListener mListener;

public interface onTaskListener {
    void onUpdateData();
}

public void setListener(onTaskListener listener){
  mListener = listener;
}
public ExecuteServerTaskBackground(Activity activity) {
    this.mActivity = activity;
    this.mContext = activity.getApplicationContext();
}

@Override
protected void onPreExecute() {
    pb = (ProgressBar) mActivity.findViewById(R.id.progressBar1);
    pb.setVisibility(View.VISIBLE);
}

@Override
protected Void doInBackground(Void... params) {
    ServerAPI server = new ServerAPI(mContext);
    if (!args.isEmpty())
        server.serverRequest(action, args);
    else
        server.serverRequest(action, null);
    return null;
}

@Override
protected void onPostExecute(Void result) {
 mListener.onUpdateData();

//There is I just update view but how to update whole variables throughtout callback?        
//                tvNoOfPayments = (TextView) mActivity.findViewById(R.id.tvNoOfPaymentsValue);
//                tvFirstPayment = (TextView) mActivity.findViewById(R.id.tvFirstPaymentValue);
//                tvTotalRepayable = (TextView) mActivity.findViewById(R.id.tvTotalRepayableValue);
//
//                lvPayments = (ListView) mActivity.findViewById(R.id.lvData);
//
//                termsConditionsM = new TermsAndConditionsManager(mContext);
//
//                termsAndConditions = termsConditionsM.getTermsAndConditions();
//
//                int totalPayments = Integer.valueOf(termsAndConditions.get(ServerAPI.NO_OF_PAYMENTS));
//
//                if (totalPayments > 0) {
//                    if (termsAndConditions != null) {
//                        tvNoOfPayments.setText(termsAndConditions.get(ServerAPI.NO_OF_PAYMENTS));
//                        tvFirstPayment.setText(termsAndConditions.get(ServerAPI.FIRST_PAYMENT_DATE));
//                        tvTotalRepayable.setText("£" + termsAndConditions.get(ServerAPI.TOTAL_REPAYABLE));
//                    }
//
//                    paymentsData = termsConditionsM.getpayments();
//
//                    if (paymentsData != null) {
//                        Log.d(TAG, paymentsData.toString());
//
//                        payments = new ArrayList<Payment>();
//
//                        for (int i = 1; i <= totalPayments; i++) {
//                            paymentValues = new Payment();
//                            paymentValues.setPaymentID(Integer.valueOf(paymentsData.get(ServerAPI.PAYMENT_NO + "_" + i)));
//                            paymentValues.setPaymentDate(paymentsData.get(ServerAPI.PAYMENT_DATE + "_" + i));
//                            paymentValues.setPaymentTotalAmount(paymentsData.get(ServerAPI.PAYMENT_TOTAL + "_" + i));
//                            payments.add(paymentValues);
//                        }
//
//                        PaymentAdapter adapter = new PaymentAdapter(mContext, R.layout.custom_loan_item, payments);
//                        lvPayments.setAdapter(adapter);
//                    }
    //             

   }

    pb.setVisibility(View.GONE);
    super.onPostExecute(result);
}
Viktor M.
  • 4,393
  • 9
  • 40
  • 71

1 Answers1

26

Without taking your code in consideration I will post the most essential to make a functional callback.


TestFragment:

public class TestFragment extends Fragment {

    /* Skipping most code and I will only show you the most essential. */    
    private void methodThatStartsTheAsyncTask() {
        TestAsyncTask testAsyncTask = new TestAsyncTask(new FragmentCallback() {

            @Override
            public void onTaskDone() {
                methodThatDoesSomethingWhenTaskIsDone();
            }
        });

        testAsyncTask.execute();
    }

    private void methodThatDoesSomethingWhenTaskIsDone() {
        /* Magic! */
    }

    public interface FragmentCallback {
        public void onTaskDone();
    }
}

TestAsyncTask:

public class TestAsyncTask extends AsyncTask<Void, Void, Void> {
    private FragmentCallback mFragmentCallback;

    public TestAsyncTask(FragmentCallback fragmentCallback) {
        mFragmentCallback = fragmentCallback;
    }

    @Override
    protected Void doInBackground(Void... params) {
        /* Do your thing. */
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        mFragmentCallback.onTaskDone();
    }
}
davidcondrey
  • 34,416
  • 17
  • 114
  • 136
Simon Zettervall
  • 1,764
  • 1
  • 15
  • 31
  • That is what I have now. And this part is working but I want to catch message not in the same place where was called AsyncTask. MY structure is MainFragment(here I want to catch message) -> DialogFragment(call asynctask)->async task(processing and return result in min fragment). Now, callback is working normally only for connection between DialogFragment and AsyncTask, but how to send result directly to mainFragment? – Viktor M. Mar 07 '13 at 13:33
  • Either if it is possible to do only throughout two callbacks(1 callback - AsyncTask ->DialogFragment, 2 callback - DialogFragment->MainFragment) then how to send listener to DialogFragment. It`s not possible to use setLIstener as it was in AsyncTask. – Viktor M. Mar 07 '13 at 13:34
  • @user1376885 Do you save the result to a database or do you load something that cannot be saved? Good practice is to keep a "stateless" approach meaning you shall not send stuff directly between Fragments or Activities. You will want to save it to a database and when the Fragment/Activity start you read the database and load the values and present it. So, what are you doing in your AsyncTask? It looks like you can save it to a database. – Simon Zettervall Mar 07 '13 at 13:41
  • All data temporary saved in SharedPreferences. That`s why it isn`t a problem to get all necessary data. The problem is only that new data(which again was saved in sharedpreferences and was replaced with old view`s data in the process of async task) losts(view`s data) when I go to another one fragment and come back again to MainFragment(this happens due to that I call prepareData() only in onCreate of MainFragment). That`s to say when I come back to MainFragment onCreate method will not call again, it can be onStart or onResume only. I want to update data when AsyncTask finish – Viktor M. Mar 07 '13 at 13:52
  • @user1376885 I see, then you should save the data to a database instead of SharedPreferences and when your Fragment starts you load that data and keep the Fragments "stateless". And when your AsyncTask is done you call MainActivity which starts the MainFragment. – Simon Zettervall Mar 07 '13 at 13:54
  • Thanks, it helps me a bit. One question concerning data storage, why it`s better to use db than shared preferences. If I have only session data(temporary), what is for db? Is it not too complexity for such a task like this? Another moment what way is more safely(data protection) for user? If there is such a way at all, as I know it`s not a big problem to hack something(extract code from apk, may be the same thing is possible to do with db or shared preferences)? What way is better for performance too from your experience? – Viktor M. Mar 07 '13 at 15:16
  • @user1376885 SharedPreferences is good when you have small stuff to save but I generally do not like it because it is good practice to good for the future, and that means that you should code so it is easy to add stuff. It is easier to handle data in a database than in SharedPreferences. But if it as you say that it is very little to save then go ahead and use the SharedPreferences. I use databases more because I think they are more secure and it is easy to export them and make backups if you have a rooted mobile phone. In short, more features are available for databases. – Simon Zettervall Mar 07 '13 at 15:46
  • @user1376885 Do not have sensitive data in the phone because as you said it is easy to hack. You can use Proguard to obfuscate the code making it harder to decrypt. But yes, it is possible with a rooted phone to extract data. So if you have sensitive information you would have a server where you store the data and you connect to it, that is the safest way I know. About performance, I think databases are better because it has more features and performance is also how you work and not only how fast the phone is. By having a database you have it easier to work and that is increased performance. – Simon Zettervall Mar 07 '13 at 15:49
  • Thank you for you opinion. Yes, I use server to get data and save everything(only session data and some temporary things) in shared. But I`ll try to use db in future, may be, when decide to refactor current project because right now I see that it`s quite routine to construct shared preferences to save and get something parsing whole shared preference for one thing. – Viktor M. Mar 07 '13 at 16:21
  • @user1376885 You are welcome. That is a good conclusion you have come to! I wish you luck. – Simon Zettervall Mar 07 '13 at 16:24
  • Yeah, you saved me lots of time! Thanks @SimonZettervall – Tobi Jun 17 '15 at 18:27