My app uses a Navigation Drawer with several menu items which basically opens different Fragments. Each fragments may run an AsyncTask that shows Toast message afterwards. However, when the user tries to open a fragment while another fragment is running, I get a nullpointer error which is understandable since the original fragment has been detached.
However, is it possible to have the Toast show even then? I basically has this code when an item is clicked on the navigator.
public void setFragment(Fragment fragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content, fragment)
.commit();
}
Then on the fragment is just a simple asynctask that shows a Toast message onPostExecute.
Toast.makeText(getContext(), t.getLocalizedMessage(), Toast.LENGTH_LONG).show();
Appreciate any help.
UPDATE (03/28/2018)
I tried using listener and it seems to work.
I basically have a BaseFragment in which all my fragments extends. I just added a callback in it.
public class BaseFragment extends Fragment {
private OnToast callback;
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
callback = (BaseFragment.OnToast) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement OnAuthenticateListener");
}
}
public OnToast getCallback() {
return callback;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
}
public interface OnToast {
void toast(String message);
}
}
In my actual fragment, I have:
public class JailsFragment extends BaseFragment {
private List<Jail> jailList = new ArrayList<>();
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setTitle("Jails");
setAdapter(new JailAdapter());
if (savedInstanceState == null) {
onSwipeRefresh();
}
}
@Override
public void onSwipeRefresh() {
super.onSwipeRefresh();
setRefreshing(true);
jailList.clear();
FreeNAS.getInstance().getJails().getJails(new Callback<List<Jail>>() {
@Override
public void onResponse(Call<List<Jail>> call, Response<List<Jail>> response) {
if (response.code() == 200) {
jailList.addAll(response.body());
notifyDatasetChanged();
} else {
//Toast.makeText(getContext(), response.message(), Toast.LENGTH_LONG).show();
getCallback().toast(response.message());
}
setRefreshing(false);
}
@Override
public void onFailure(Call<List<Jail>> call, Throwable t) {
//Toast.makeText(getContext(), t.getLocalizedMessage(), Toast.LENGTH_LONG).show();
getCallback().toast(t.getLocalizedMessage());
setRefreshing(false);
}
});
}
}
Then in my MainActivity:
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, BaseFragment.OnToast {
private SharedPreferences pref;
private Toolbar toolbar;
private DrawerLayout drawer;
private ActionBarDrawerToggle toggle;
private NavigationView navigationView;
@Override
public void toast(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pref = PreferenceManager.getDefaultSharedPreferences(this);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
if (savedInstanceState == null) {
setFragment(new AuthenticateFragment());
}
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
final int id = item.getItemId();
drawer.closeDrawer(GravityCompat.START);
drawer.postDelayed(new Runnable() {
@Override
public void run() {
if (id == R.id.nav_plugins) {
setFragment(new PluginsFragment());
} else if (id == R.id.nav_jails) {
setFragment(new JailsFragment());
} else if (id == R.id.nav_jails_configuration) {
setFragment(new ConfigurationFragment());
} else if (id == R.id.nav_services) {
setFragment(new ServicesFragment());
} else if (id == R.id.nav_alerts) {
setFragment(new AlertsFragment());
} else if (id == R.id.nav_updates) {
setFragment(new UpdatesFragment());
}
}
}, 300);
return true;
}
public void setFragment(Fragment fragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content, fragment)
.commit();
}
}
I assume you are receiving a nullPointer exception here
Toast.makeText(getContext(), t.getLocalizedMessage(), Toast.LENGTH_LONG).show();
I would say that you are receiving the null pointer because you do not have the context anymore.
The possible solution is:
1- Declare you Application as singleton, then you could use MyApplication.getInstance() and use this as a context for the Toast message. Check this link to see how to create this singleton. Also it is recommended to use the applicationContext instead of the activity for toast messages
As far as I understand getContext() and getActivity() will return the same object, which is the activity so that will not help.
EDIT 1:
The problem with the solution you are proposing (having a reference to the Activity, in this case as an interface) is that you may have memory leaks. When I say "may have" is because you are doing something special when you put this line:
setRetainInstance(true);
You may want to read this post.
The memory leak will be generated when you do not need the Activity anymore (let's say you move to another activity) but the fragment (the object) is not being destroyed yet for some reason. Another example is with a low connection to Internet, then the user may leave the screen to move to another one, but your fragment is still alive and so your activity can not be garbage collected. This is why I do not recommend your approach :).
PS: sorry for the late answer, I was on vacation :P.