I have two fragments in my Activity : Fragment_A and Fragment_B.
In Fragment A, I created an AsyncTask (when the user "swipeRefreshes" the screen). In the onPostExecute() of this task, I want to display a Toast :
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
MakeRequestTask() {
//Some stuff
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
}
#Override
protected void onCancelled() {
swipeRefreshLayout.setRefreshing(false);
//Some stuff
}
}
If the user changes from Fragment_A to Fragment_B before the AsyncTask finishes, I get a crash:
java.lang.IllegalStateException: Fragment Fragment_A not attached to a context.
I know how to avoid the crash (by adding the condition isAdded()), but I want my Toast to be displayed no matter which Fragment is displayed/alive on top of my Activity.
1stly I would like to suggest you, please make your MakeRequestTask inner class as static as this can be a memory leak.
For your question, You need to pass the context to the class like below:
private static class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
private WeakReference<Context> weakReference;
MakeRequestTask(Context context) {
//Some stuff
weakReference = new WeakReference<>(context);
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
// swipe layout will not be shown if fragment is not visible or destroyed
if (isFragmentVisible) {
swipeRefreshLayout.setRefreshing(false);
}
// toast will be shown no matter what fragment is visible
if (weakReference != null) {
Context context = weakReference.get();
if (context != null) {
Toast.makeText(context, "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
}
}
#Override
protected void onCancelled() {
if (isFragmentVisible) {
swipeRefreshLayout.setRefreshing(false);
}
//Some stuff
}
}
Try this way
Declare a boolean in Fragment_A
private boolean isFragmentVisible=false;
In Fragment_A class
Make this boolean true in onCreateView() of this Fragment_A
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.lyourlayout, container, false);
isFragmentVisible = true;
return view;
}
And make this boolean false in onDestroyView() of this fragment A
#Override
public void onDestroyView() {
super.onDestroyView();
isFragmentVisible = false;
}
Finally use it in Asyntask of Fragment_A like this
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
MakeRequestTask() {
//Some stuff
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
// swipe layout will not be shown if fragment is not visible or destroyed
if(isFragmentVisible){
swipeRefreshLayout.setRefreshing(false);
}
// toast will be shown no matter what fragment is visible
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
#Override
protected void onCancelled() {
if(isFragmentVisible){
swipeRefreshLayout.setRefreshing(false);
}
//Some stuff
}
}
Or you can just use and interface or an EventBus in the onPostExecute method, and show the Toast inside the activity.
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
activityContractInterface.showToast()
}
}
And in your Activity:
#Override
public void showToast(){
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
Or the EventBus approach:
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
EventBus.getDefault().post(new ShowToastEvent())
//just create an empty class, hope you know what EventBus is
}
And in your activity:
#Subscribe(threadMode = ThreadMode.Main){
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
EventBusLibrary
Related
This is a very strange behavior and I don't know how to fix it.
I have an Activity as a Presenter (In a MVP Architecture).
When the activity starts, I attach a Fragment as a View. The fragment itself is very simple.
public class CurrentSaleFragment extends BaseFragment {
private MainMVP.SalesPresenterOps salesPresenterOps;
private SaleAdapter adapter;
private ListView lv;
#BindView(R.id.btn_sell)
FloatingActionButton btnAdd;
public static CurrentSaleFragment newInstance(){
CurrentSaleFragment fragment = new CurrentSaleFragment();
Bundle arguments = new Bundle();
arguments.putInt(LAYOUT_RES_ID, R.layout.fragment_quick_sale );
fragment.setArguments(arguments);
return fragment;
}
#Override
protected void init() {
super.init();
lv = (ListView)view.findViewById(R.id.lv_sale);
}
#OnClick(R.id.btn_sell)
public void addToSale(View view){
mPresenter.moveToFragment(SellProductFragment.newInstance());
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
salesPresenterOps = (MainMVP.SalesPresenterOps)context;
}
#Override
public void onDetach() {
salesPresenterOps = null;
super.onDetach();
}
}
The BaseFragment from which this fragmend extends :
public class BaseFragment extends Fragment implements MainMVP.RequiredViewOps, View.OnClickListener,
LoaderRequiredOps{
protected View view;
protected MainMVP.PresenterOps mPresenter;
protected final static String LAYOUT_RES_ID = "layout_res_id";
#Override
public void showOperationResult(String message, final long rowId) {
Snackbar.make(view, message, Snackbar.LENGTH_LONG).setAction(
R.string.see, new View.OnClickListener() {
#Override
public void onClick(View v) {
onOperationResultClick(rowId);
}
}
).show();
}
#Override
public void showSnackBar(String msg) {
Snackbar.make(view, msg, Snackbar.LENGTH_SHORT).show();
}
#Override
public void showAlert(String msg) {}
protected void onOperationResultClick(long rowId){}
#Override
public void onAttach(Context context) {
super.onAttach(context);
mPresenter = (MainMVP.PresenterOps)context;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
this.view = inflater.inflate(getArguments().getInt(LAYOUT_RES_ID), null);
init();
return view;
}
protected void addToClickListener(View ... params){
for (View v : params){
v.setOnClickListener(this);
}
}
protected void init() {
if (view != null){
ButterKnife.bind(this, view);
}
}
#Override
public void onDetach() {
mPresenter = null;
Log.d(getClass().getSimpleName(), "Fragment was detached");
super.onDetach();
}
#Override
public void onClick(View v) {}
#Override
public void onPreLoad() {
Dialogs.buildLoadingDialog(getContext(), "Loading...").show();
}
#Override
public void onLoad() {}
#Override
public void onDoneLoading() {
Dialogs.dismiss();
}
}
When I enter the method 'moveToFragment()' I just replace CurrentSaleFragment for a new Fragment:
protected void addFragment(BaseFragment fragment){
mView = fragment;
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder,
fragment, null).addToBackStack(null).commit();
}
Then the new fragment is attached:
public class SellProductFragment extends BaseFragment{
private ListView listView;
private ProductListAdapter adapter;
private MainMVP.SalesPresenterOps mSalesPresenter;
public static SellProductFragment newInstance(){
SellProductFragment fragment = new SellProductFragment();
Bundle arguments = new Bundle();
arguments.putInt(LAYOUT_RES_ID, R.layout.fragment_inventory);
fragment.setArguments(arguments);
return fragment;
}
private void reload(){
final Loader loader = new Loader(this);
loader.execute();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
mSalesPresenter = (MainMVP.SalesPresenterOps)context;
}
#Override
protected void init() {
super.init();
listView = (ListView)view.findViewById(R.id.lv_inventory);
reload();
FloatingActionButton button = (FloatingActionButton)view.findViewById(R.id.btn_add);
addToClickListener(button);
}
#Override
public void onLoad() {
adapter = new ProductListAdapter(getActivity().getApplicationContext(), R.layout.row_product_item,
mSalesPresenter.getProducts());
try{
updateListView();
}catch (Exception e){
Log.w(getClass().getSimpleName(), e.getMessage());
}
}
private void updateListView(){
if (adapter != null && listView != null){
listView.setAdapter(adapter);
}else{
throw new RuntimeException();
}
}
}
See that This fragment also extends from BaseFragment and implements LoaderRequiredOps. The interface is used to 'load' any data. It adds a dialog and updated the adapter when the loading is done:
public class Loader extends AsyncTask<Void, Void, Void> {
private LoaderRequiredOps presenter;
public Loader(LoaderRequiredOps presenter){
this.presenter = presenter;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
presenter.onPreLoad();
}
#Override
protected Void doInBackground(Void... params) {
presenter.onLoad();
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
presenter.onDoneLoading();
presenter = null;
}
}
Now, when I try to execute the method reload() from the SellProductFragment i get the 'Only the original thread that created a view hierarchy can touch its views.'
This does not happen if the SellProductFragment is attached first instead of CurrentSaleFragment.
What is happening here?
Your Async Loader class calls the presenters method onLoad() from a background thread during doInBackground().
My guess is that in the onLoad() method of the presenter, a view is referenced.
In order to change the view at this point, post the view logic as a Runnable to the UI thread (you said your presenter is the activity, so this should be possible from the onLoad method).
#Override
public void onLoad() {
runOnUiThread(new Runnable() {
#Override
public void run() {
// Your ui code here...
}
});
// Rest of your code here...
}
For an unknown reason, an unidentified configuration allows to execute the setting of an adapter for a ListView on the doInBackground() method.
Moved it to onPostExecute() and now it's working
I don't want to implement an interface directly in asynctask class like this:
new AsyncTask<Void, Void, Void>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
swipeRefreshLayout.setRefreshing(true);
}
#Override
protected Void doInBackground(Void... params) {
retrofitCallBackUtil.getLastTenMessageCallBack(AppConfig.USER_ID, userId, offsetNumber, service, new RetrofitResponseCallBack() {
#Override
public void onSuccess(ArrayList<Message> messages) {
messageAdapter.insertToTheFirst(messages);
}
#Override
public void onFailure() {
}
});
offsetNumber += 5;
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
messageAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
}.execute();
I just to implement it by main class and put this into this parameter like this:
retrofitCallBackUtil.getLastTenMessageCallBack(AppConfig.USER_ID, userId, offsetNumber, service, this);
but I cannot do it inside asynctask method. So anyone can give me some solution?
I assume you have these code in MainActivity (or something similar).
Let's implement the interface you need to pass into getLastTenMessageCallBack in this Activity by implements RetrofitResponseCallBack {...}
Now, in your Asyntask doInBackground method, call
retrofitCallBackUtil.getLastTenMessageCallBack(AppConfig.USER_ID, userId, offsetNumber, service, MainActivity.this)
Voila, you don't have to create anonymous class inside your Asyntask anymore.
Note that, you have to move and place the fields/params properly, just the matter of being right place, no big deal :D
Create a class separately for AsyncTask like this. This AsyncTask will process the data and publish the data who implements the interface defined inside the AsyncTask . In this case interface defined is DataDownloadListener
public class GetFoldersAsync extends AsyncTask<Integer,Boolean,Boolean> {
Context context;
ProgressDialog dialog;
ArrayList mFolderDataLevel;
public GetFoldersAsync(Context context){
this.context=context;
}
public static interface DataDownloadListener {
public void dataDownloadedSuccessfully(ArrayList data);
public void dataDownloadFailed();
}
DataDownloadListener dataDownloadListener;
public void setDataDownloadListener(DataDownloadListener dataDownloadListener) {
this.dataDownloadListener = dataDownloadListener;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = ProgressDialog.show(context, "Please wait", "Processing.........");
}
#Override
protected Boolean doInBackground(Integer... params) {
Log.v(Constants.BLL_LOG, "ExplorerDBOperation doInBackground ");
mFolderDataLevel=new ArrayList();
// Process to populate mFolderDataLevel
return true;
}
#Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
if(dialog!=null && dialog.isShowing())
{dialog.dismiss();}
Log.v(Constants.BLL_LOG, "ExplorerDBOperation onPostExecute ");
if(mFolderDataLevel!=null && mFolderDataLevel.size()>0){
dataDownloadListener.dataDownloadedSuccessfully(mFolderDataLevel);
}else{
dataDownloadListener.dataDownloadFailed();
}
}
}
Now in the caller Activity, call this method when the AsyncTask needs to process and get back data to same activity.
public class HomeActivity extends AppCompatActivity {
//Other necessary methods goes here .....
//call this method for processing AsyncTask and to get back data
public void getFolderData(int parentID, int callerID){
Log.v(Constants.BLL_LOG,"parentID="+parentID+" , callerID="+callerID);
mCallerID=callerID;
GetFoldersAsync getFolders = new GetFoldersAsync(this);
getFolders.setDataDownloadListener(new GetFoldersAsync.DataDownloadListener()
{
#SuppressWarnings("unchecked")
#Override
public void dataDownloadedSuccessfully(ArrayList data) {
Log.v(Constants.BLL_LOG,"getFolderData dataProcessSuccessfully");
// Success data with populated ArrayList to process further
}
#Override
public void dataDownloadFailed() {
//Failure
Log.v(Constants.BLL_LOG,"getFolderData dataProcessFailed()");
}
});
getFolders.execute(callerID);
}
}
I have a main activity, which has a fragment inside, that calls an Asynctask.
Main Activity - The main activity has a ViewPager that loads the fragment.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.viewPager = (ViewPager)findViewById(R.id.pager);
this.mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
/**
* on swiping the viewpager make respective tab selected
* */
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
//actionBar.setSelectedNavigationItem(position);
//Toast.makeText(getApplicationContext(), "this is my Toast message!!! =)",
//Toast.LENGTH_LONG).show();
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
Fragment - Makes Call to service
public class SomeFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.article_view, container, false);
ServiceHelper srv = new ServiceHelper(getActivity(), "GetHomeImage", postParameters, 2);
AsyncTask<String, Void, String> request = srv.execute();
return view;
}
}
Async Task Class - Show Progressdialog and make requests
public class ServiceHelper extends AsyncTask<String, Void, String> {
public ServiceHelper(Context c, String method, HashMap<String, Object> parameters, int requestType){
context = c;
this.method = method;
this.parameters = parameters;
this.requestType = requestType;
}
protected void onPreExecute(){
progressDialog = ProgressDialog.show(context, "Requisição", "Chamando Serviço", true, false);
}
protected String doInBackground(String... params) {
do stuff...
}
protected void onPostExecute(String result) {
progressDialog.dismiss();
}
}
The problem I'm facing is that the fragment is called, the request is made, but the Progessdialog only appears when the fragment is shown.
It's possible to show the Progressdialog when the call is made?
Thanks.
I think the simple way here is show progress dialog before call to execute AsyncTask. for closing progress dialog you should add a Listener to ServiceHelper and listen it to dismiss dialog on cancel or completion of task. here is code:
final Dialog progressDialog = ProgressDialog.show(context, "Requisição", "Chamando Serviço", true, false);
ServiceHelper srv = new ServiceHelper(getActivity(), "GetHomeImage", postParameters, 2);
srv.setListener(new ServiceHelperListener() {
public void onCancel() {
progressDialog.dismiss();
}
public void onCompelte() {
progressDialog.dismiss();
}
});
AsyncTask<String, Void, String> request = srv.execute();
and ServiceHelper class and Listener:
public class ServiceHelper extends AsyncTask<String, Void, String> {
private ServiceHelperListener mListener;
public ServiceHelper(Context c, String method, HashMap<String, Object> parameters, int requestType){
context = c;
this.method = method;
this.parameters = parameters;
this.requestType = requestType;
}
public void setListener(ServiceHelperListener listener) {
this.mListener = listener;
}
protected void onPreExecute(){
}
protected String doInBackground(String... params) {
do stuff...
}
protected void onPostExecute(String result) {
if (mListener != null) {
mListener.onCompelte();
}
}
#Override
protected void onCancelled(String s) {
super.onCancelled(s);
if (mListener != null) {
mListener.onCancel();
}
}
}
public interface ServiceHelperListener {
public void onCancel();
public void onCompelte();
}
I'm with a little problem that is driving me crazy. I'm using a AsyncTask in a retained Fragment to update a progressDialog in my activity. I'm using callbacks to send the progress from my fragment.
The problem is: When I rotate my screen it simply stops do update the progressDialog in the recreated activity. It seems like onProgressUpdate stops to being called in the rotated activity.
the relevant part of the code is shown below:
Worker Fragment
public class WorkerFragment extends Fragment {
Context mContext;
...
public static interface TaskCallbacks {
void onPreExecute();
void onProgressUpdate(int... progress);
void onPostExecute();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (TaskCallbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
}
//inner class
class DownloadFileFromURL extends AsyncTask<String, Int, Void> {
#Override
public void onPreExecute() {
if (mCallbacks != null) {
mCallbacks.onPreExecute();
}
#Override
public String doInBackground(String... f_url) {
... //some verifications of files
if(!localFile.exists()){
//http connection, check, buffer, inputstream, etc...
publishProgress((int)((bytesDownloaded*100)/remoteFileSize));
...
}
}
#Override
protected void onProgressUpdate(int... percent) {
if (mCallbacks != null) {
mCallbacks.onProgressUpdate(percent);
}
}
...
}
}
and the Activity
public class Updater extends Activity implements WorkerFragment.TaskCallbacks {
private WorkerFragment myWorker;
private ProgressDialog pDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_updater);
pDialog=null;
showDialog(1);
FragmentManager fm = getFragmentManager();
myWorker = (WorkerFragment) fm.findFragmentByTag("task");
if(myWorker == null)
{
myWorker = new WorkerFragment();
fm.beginTransaction().add(myWorker, "task").commit();
}
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case 1:
pDialog = new ProgressDialog(this);
pDialog.setMessage("Baixando arquivos de mídia");
pDialog.setIndeterminate(false);
pDialog.setMax(100);
pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pDialog.setCancelable(false);
pDialog.show();
dialogType=progress_bar_type;
return pDialog;
}
}
#Override
public void onProgressUpdate(int... progress) {
//setting progress percentage
if(pDialog!=null)
{
pDialog.setProgress(progress[0]);
}
}
#Override
public void onPostExecute(){
if(pDialog!=null)
{
dismissDialog(1);
...
}
}
Try to put this code in manifest, where you've indicated your activity
android:configChanges="orientation|screenSize"
On my main activity I have a Fragment in which I apply setRetainInstance(true) so that the AsyncTask I use into it is not disturbed by orientation change.
A lot of work is processed by the AsyncTask. That's why I would like to display a dialog with a progressBar on top of my activity.
I made some researches and I succeed in doing with a DialogFragment:
public class DialogWait extends DialogFragment {
private ProgressBar progressBar;
public DialogWait() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_wait, container);
Dialog dialog = getDialog();
dialog.setTitle("Hello");
setCancelable(false);
progressBar = (ProgressBar) view.findViewById(R.id.progress);
return view;
}
public void updateProgress(int value) {
progressBar.setProgress(value);
}
And here is my AsyncTask:
public class InitAsyncTask extends AsyncTask<Void, Integer, Void> {
private Context activity;
private OnTaskDoneListener mCallback;
private DialogWait dialog;
public InitAsyncTask(Context context, OnTaskDoneListener callback, DialogWait dialogWait) {
activity = context;
mCallback = callback;
dialog = dialogWait;
}
#Override
protected Void doInBackground(Void... params) {
doStuff();
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
dialog.updateProgress(values[0]);
}
#Override
protected void onPostExecute(Void result) {
publishProgress(100);
if(dialog != null)
dialog.dismiss();
mCallback.onTaskDone();
}
private void doStuff() {
//...
}
}
If I don't change the screen rotation, it works fine. But if I do, the dialog is dismissed and a few seconds later, I got a NullPointerEsception which nonsense since I set the condition: if(dialog != null)
What am I doing wrong?
Solution found!
I was not doing the right thing with the Fragment containing my AsyncTask.
Because, I haven't really understood the concept of orientation in Fragment, I get it thanks to this link: http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
Override onCreate, and onDestroyView methods in your DialogWait as follows:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) {
getDialog().setDismissMessage(null);
}
super.onDestroyView();
}