I am reasonably new to fragments, so I apologise if this is quite basic.
My app contains a ViewPager to swipe between several different tabs with ListViews. On clicking one particular ListView item, I would like to open a DialogFragment displaying some options. My intention is to have the DialogFragment deal with the click event and feed the selected dialog item back to the fragment that called it. I am doing this by providing a callback to MainActivity (which contains the ViewPager) and feeding that information down to the correct ViewPager fragment.
What I have noticed is that my DialogFragment is returning null on getActivity(), which I believe is because my DialogFragment is not correctly attached to my MainActivity. How can I achieve this? I find it a little harder to navigate through this issue considering I am new to callbacks and most other examples dealing with fragments are for standard fragments, not those from ViewPager.
How can my dialog identify the correct activity? Should I be attaching the fragment to a FragmentManager? If so, should it be attached to the same FragmentManager as the ViewPager fragments?
This is my code, though I'm not sure how helpful it will be.
CalibrationFragment.java - Calling the DialogFragment. (Not sure if this is the correct way to do it.)
ModeDialogFragment modeDialog = ModeDialogFragment.newInstance(R.string.mode_calibration);
modeDialog.onCreateDialog(null);
ModeDialogFragment.java - My DialogFragment
public class ModeDialogFragment extends DialogFragment{
public static ModeDialogFragment newInstance(int title) {
ModeDialogFragment frag = new ModeDialogFragment();
Bundle args = new Bundle();
args.putInt("title", title);
frag.setArguments(args);
return frag;
}
public void onAttach(Activity activity){
super.onAttach(activity);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int title = getArguments().getInt("title");
final Context mContext = getActivity();
getActivity().getFragmentManager().beginTransaction().add(newInstance(R.string.mode_calibration), "my_fragment").commit();
// Trying to add my dialog to the MainActivity, not working.
final CharSequence[] modeItems = {
"Option 1", "Option 2", "Option 3"
};
final int checkedItem = 2; // Make final for now, resolve this later
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getResources().getString(R.string.mode_calibration));
builder.setSingleChoiceItems(modeItems, checkedItem, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
// Handle the selection here
((MainActivity)getActivity()).updateModeData();
}
});
return(builder.create());
}
}
MainActivity.java
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
// Initialise variables
private Context mContext;
// [Others...]
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialisation
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
}
public Fragment findFragmentByPosition(int position) {
int viewId = R.id.pager;
//FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
makeFragmentName(viewId, position));
}
// Needed to identify a fragment from ViewPager
private static String makeFragmentName(int viewId, int position)
{
return "android:switcher:" + viewId + ":" + position;
}
public void updateModeData(){
// Make call to the fragment to deal with the data
CalibrationFragment calFragment = (CalibrationFragment) findFragmentByPosition(3);
((CalibrationFragment) calFragment).doUpdateModeData();
}
}
Any help or guidance would be appreciated!
EDIT: The problem was indeed that I was not adding the fragment to a FragmentManager and calling it correctly. This is the code I used. My code still does not run through in its entirety, but for the purpose of this question, my DialogFragment can now access MainActivity.
CalibrationFragment.java - Calling DialogFragment
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ModeDialogFragment modeDialog = ModeDialogFragment.newInstance(R.string.mode_calibration);
String title = makeFragmentName(R.id.pager, 3); // makeFragmentName as specified in MainActivity.java
modeDialog.show(ft, title);
ModeDialogFragment.java is the same as before, but with the following line removed:
getActivity().getFragmentManager().beginTransaction().add(newInstance(R.string.mode_calibration), title).commit();
You shouldn't call onCreateDialog by yourself. Just call show method with FragmentTransaction:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ModeDialogFragment modeDialog = ModeDialogFragment.newInstance(R.string.mode_calibration);
modeDialog.show(ft, null);
Right usage of DialogFragment is described here: http://developer.android.com/reference/android/app/DialogFragment.html
Related
Im having an issue that only appears after several hours of inactivity, I researched it ive tried various ways of fixing it to no avail. The issue is after my app has been dormant for several hours the references for my fragments are null, however; they still exist in the frag manager. I use the references to pull the tag, or id by findfragmentby...() so I can call specific methods within them for updating themselves and what not. The fragments are dynamic and have a UI. I have several activities and a service that are called on by the main activity. I can close the app and resume, call activities, pull info from the service, close, use the back button, all without an issue. To give you an idea of how the app is structured...
public class appClass extends Application {
public Fragment fragmentA;
public Fragment fragmentB;
public Fragment fragmentC;
#Override
public void onCreate() {
super.onCreate();
new fragmentTemplate();
fragemntA = fragmentTemplate.newInstance(getDbName(), usefuldata, "A List");
new fragmentTemplate();
fragemntB = fragmentTemplate.newInstance(getDbName(), usefuldata, "B list");
new fragmentTemplate();
fragemntC = fragmentTemplate.newInstance(getDbName(), usefuldata, "C list");
}
}
Moving on to activity where fragments are used in a viewager...
public class mainActivity extends AppCompatActivity implements ...listeners{
appClass myAppClass;
FragmentManager FragMgr;
ViewPager viewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myAppClass = (appClass) getApplication();
setTheme(myAppClass.getAppTheme());
setContentView(R.layout.activity_my_layout);
//toolbar actionbar stuff
FragMgr = getSupportFragmentManager();
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new ViewPagerAdapter(FragMgr));
//tab setup
}
//inner class pager adapter is here
}
This is my pager adapter
class ViewPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener{
Fragment fragment;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (myAppClass.fragmentA != null) {
fragment = myAppClass.fragemntA ;
}
break;
case 1:
if (myAppClass.fragmentB != null) {
fragment = myAppClass.fragmentB ;
}
break;
case 2:
if (myAppClass.fragmentC != null) {
fragment = myAppClass.fragmentC ;
}
break;
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
}
I have a FAB and its listener looks like this
#Override
public void onClick(View v) {
Fraggment fragment;
int i = viewPager.getCurrentItem();
if (v.getId() == floatingActionButton.getId()) {
switch (i) {
case 0:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentA.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 1:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentB.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 2:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentC.getTag());
fragment.addItem(fragment.getSomeString());
break;
}
}
}
code for a fragment
public class fragmentTemplate extends Fragment implements RecyclerAdapter.aListener {
private appClass myAppclassReference;
private RecyclerView recyclerView;
private View view;
private FragmentTitle;
public static fragmentTemplate newInstance(String a, String b, String c) {
Bundle args = new Bundle();
args.putString(KEY_A, a);
args.putString(KEY_B, b);
args.putString(KEY_C, c);
fragmentTemplate fragment = new fragmentTemplate();
fragment.setArguments(args);
return fragment;
}
public String getFragmentTitle() {
return fragmentTitle;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) {
view = inflater.inflate(R.layout.list, container, false);
myAppclassReference= ((appClass) getActivity().getApplication());
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
//get list is a local function that loads a list from a db source
RecyclerAdapter recycler = new RecyclerAdapter(getActivity(), getList());
recycler.setListener(this);
recyclerView.setAdapter(recycler);
recyclerView.setLayoutManager(newLearLayoutManager(getActivity()));
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {}};
return view;
}
}
When things go wonky the app does not crash right away, the tabs still scroll, the viewpager still scrolls, but it is empty, its not until I hit the FAB do I get a nullpointerexception, trying to invoke a method on a nullpointer reference within the onClick Listener does it actually crash.
This is happening because you are messing up with the way that the Android Framework handles Fragments for you. When the ViewPagerAdapter gets Fragments from you in getItem(int), it's using the FragmentManager that you gave it to attach the Fragments. Once the Activity is killed because of low memory, the FragmentManager will automatically create new instances of your Fragments. At this point there are two copies of the fragments, the ones the FragmentManager created and the ones you recreated in your appClass.
You should never keep references to your Fragments. The FragmentManager is free to destroy them and create new ones. If you need to communicate between the Activity and the Fragments in the ViewPager, you can either make the Fragment ask its Activity for commands, use an Event Bus, or explore the sketchy solutions here.
This is the situation. I have an activity with 5 tabs (5 fragments). One of the fragments uses a RecyclerView to show a list of posts (like a forum). When users tap on an item menu, I need to implement the possibility of send a new post and update the list. I have no problems sending the new post to my DB and managing the response, but I don’t know how to, from the activity, tell the fragment to tell de adapter to update the list. I hope I’m explaining myself (English is not my first language).
This is my code so far.
The activity:
public class UsuarioActivity extends AppCompatActivity {
public ViewPager mPager;
public SlidingTabLayout mTabs;
...
protected void onCreate (Bundle savedInstanceState) {
mPager = (ViewPager) findViewById(R.id.pagerUsuario);
mTabs = (SlidingTabLayout) findViewById(R.id.tabsUsuario);
...
mPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));
mTabs.setCustomTabView(R.layout.custom_tab_view, R.id.tabText);
mTabs.setDistributeEvenly(true);
mTabs.setBackgroundColor(getResources().getColor(R.color.white));
mTabs.setViewPager(mPager);
}
class MyPagerAdapter extends FragmentStatePagerAdapter {
FragmentManager fragmentManager;
public MyPagerAdapter (FragmentManager fm) {
super(fm);
fragmentManager = fm;
}
#Override
public Fragment getItem (int position) {
Fragment fragment = null;
switch (position) {
case TAB_INFO:
fragment = FragmentUserInfo.getInstance(usuario);
break;
case TAB_COM:
fragment = FragmentUserComment.getInstance(usuario);
break;
case TAB_SEG:
fragment = FragmentUserSeg.getInstance(usuario);
break;
case TAB_IM:
fragment = FragmentUserImages.getInstance(usuario);
break;
case TAB_FILES:
fragment = FragmentUserFiles.getInstance(usuario);
break;
}
return fragment;
}
#Override
public CharSequence getPageTitle (int position) { ... }
#Override
public int getCount () { return TAB_COUNT; }
}
}
The fragment:
...
import android.support.v4.app.Fragment;
...
public class FragmentUserComment extends Fragment {
private RecyclerView recyclerView;
private RVIndexCommetAdapter indexCommetAdapter;
private static final String KEY_USUARIO = "KEY_USUARIO";
public static FragmentUserComment getInstance (Usuario usuario) {
FragmentUserComment fragmentUserComment = new FragmentUserComment();
Bundle args = new Bundle();
args.putLong(KEY_USUARIO, idusuario);
fragmentUserComment.setArguments(args);
return fragmentUserComment;
}
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
usuario = getArguments().getLong(KEY_USUARIO);
View layout = inflater.inflate(R.layout.fragment_user_comment, container, false);
recyclerView = (RecyclerView) layout.findViewById(R.id.recViewUsuariosComment);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
indexCommetAdapter = new RVIndexCommetAdapter(getContext());
indexCommetAdapter.setListaComentarios(usuario.list);
recyclerView.setAdapter(indexCommetAdapter);
}
I think it’s not necessary to copy here the adapter class since my main problem is how to send the new post data from the activity to the fragment.
PS: In case anyone suggests this to me, I know I could send the new post to my DB and managing the response directly in the fragment, not in the activity. In fact, that was my first try, but onOptionsItemSelected doesn’t catch the event of tapping the item menu and I lost enough time trying to solve it (reading lots of questions here included), so I tried this new approach.
Thanks in advance.
To refresh the list from Activity you should make your Adapter static and call from your Activity notifyDataSetChanged(), it probably look like this
FragmentUserComment.mAdapter.notifyDataSetChanged();
To send data from Activity to Fragment you could make class with static variables and write them datas in your Activity and read them in Fragment. It's rather not the most professional method, but it works and it's very easy to implement.
create the method in the FragmentUserComment,look like this:
public void updateList(datas){
mAdapter.setdatas(datas);
mAdapter.notifyDataSetChanged();
}
you can call updateList() in activity when you refresh the list
((FragmentUserComment)fragment).updateList(datas);
I'm making an app with actionbar tabs, and the code of each fragment is almost the same... So i thought about using 1 fragment (passing the tab position to the fragment so it will know what to do on onCreateView) but some developer said it was a pain to save the tab state.
I also thought about making a class and extend each fragment from there, still, the used code is almost the same and i ran into some troubles trying this.
So I'm not sure about the best way to do this, the app is working... but i hope you can help me to improve my design. Thanks in advance.
You create similar fragments by creating a static method to create the fragment and set arguments. When oncreate runs you access the arguments. Pretty much the same as viewpager.
public class MainActionBarTabListFragment extends ListFragment {
public static MainActionBarTabListFragment newInstance(int sortOrder,ArrayList<String> tabsList) {
MainActionBarTabListFragment f = new MainActionBarTabListFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putInt(SORT_ORDER, sortOrder);
args.putStringArrayList(TAB_NAME, tabsList);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle b = this.getArguments();
if (b != null) {
mSortOrder = b.getInt(SORT_ORDER, 0);
tabsList = b.getStringArrayList(TAB_NAME);
}
}
public class MainActionBarTabsPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<String> tabsList;
public MainActionBarTabsPagerAdapter(FragmentManager fm, ArrayList<String> tabsList) {
super(fm);
this.tabsList = tabsList;
}
/** This method will be invoked when a page is requested to create */
#Override
public Fragment getItem(int fragmentPage) {
MainActionBarTabListFragment fragment0 = MainActionBarTabListFragment
.newInstance(fragmentPage, tabsList);
return fragment0;
}
#Override
public int getCount() {
return tabsList.size();
}
}
I'm trying to display a ListFragment that'll show user comments from within another Fragment. For testing purposes, I'm calling the ListFragment using an "onClick" method from within the parent Fragment. However, I can't seem to get the ListFragment to show, as it has no "show" method. Are there any obvious problems with my code that I can fix?
This is the method called when I click a button to show the ListFragment.
private void showComments(JSONArray comments) {
ListFragment newFragment = CommentsFragment.newInstance(comments);
}
And here is the ListFragment itself.
public class CommentsFragment extends ListFragment {
public static CommentsFragment newInstance(JSONArray passedComments) {
CommentsFragment f = new CommentsFragment();
ArrayList<String> adapter = convertJSON(passedComments);
Bundle args = new Bundle();
args.putStringArrayList("adapter", adapter);
f.setArguments(args);
return f;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle args = new Bundle();
ArrayList<String> commentsArray = args.getStringArrayList("adapter");
ArrayAdapter<String> commentsAdapter = new ArrayAdapter<String>(
getActivity(), R.layout.comments_list,
commentsArray);
setListAdapter(commentsAdapter);
}
You have to commit the ListFragment with a FragmentManager, and also have a layout in the XML that will contain it.
Firstly add a FrameLayout to the XML where you want the ListFragment placed, and afterwards use getChildFragmentManager() to get a FragmentManager, that you can place the ListFragment with.
Try like this
private void showComments(JSONArray comments) {
ListFragment newFragment = CommentsFragment.newInstance(comments);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(android.R.id.content, newFragment);
fragmentTransaction.commit();
}
I have a class "bancoActivity" that extends Fragment implements ActionBar.TabListener that calls another class "pagamentos" extends Fragment implements ActionBar.TabListener.
When I'm in class "pagamentos" and click on the physical button "back" nothing happens, and when i click again the application finish.
I leave there my code so that your can analyze.
Obrigado.
part of the bancoActivity:
#Override
public void onItemClick(AdapterView<?> customviewadapter, View view, int position, long id) {
listViewItem item = items.get(position);
String Titulo = item.Title;
if(Titulo.equals("Pagamentos")) {
FragmentManager fragmentManager2 = getFragmentManager();
FragmentTransaction fragmentTransaction2 = fragmentManager2.beginTransaction();
pagamentos fragment2 = new pagamentos();
fragmentTransaction2.hide(bancoActivity.this);
fragmentTransaction2.add(android.R.id.content, fragment2);
fragmentTransaction2.addToBackStack("banco");
fragmentTransaction2.commit();
}
}
part of the pagamentos:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setContentView(R.layout.pagamentos);
FragmentManager fm = getFragmentManager();
the two activities extends and implements:
public class pagamentos extends Fragment implements ActionBar.TabListener{
public class bancoActivity extends Fragment implements ActionBar.TabListener
I'm a newbie too, but I'll take a stab at it...
I think you need to instantiate both fragments from the activity that holds them, and use 1 fragment manager to do the switching. Its hard for me to tell without your whole code, but I have a similar setup working, here is how:
public class MainActivity extends Activity implements ActionBar.TabListener {
public static final int SEARCH_FRAG = 1;
public static final int MAP_FRAG = 2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragMan = (FragmentManager) getFragmentManager();
mapFrag = ExtendedMapFragment.newInstance();
searchFrag = SearchFragment.newInstance();
if (mapShown == false) {
swapFrags(SEARCH_FRAG);
} else {
swapFrags(MAP_FRAG);
}
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(true);
searchTab = actionBar.newTab()
.setText(R.string.search_tab_label)
.setTabListener(this);
actionBar.addTab(searchTab);
mapTab = actionBar.newTab()
.setText(R.string.map_tab_label)
.setTabListener(this);
actionBar.addTab(mapTab);
if (savedInstanceState != null) {
actionBar.setSelectedNavigationItem(savedInstanceState.getInt("currentTab"));
}
actionBar = null;
}
public void swapFrags(int whatFrag) {
if (whatFrag == SEARCH_FRAG) {
//switch to frag 1, ie searchFrag
FragmentTransaction trans = fragMan.beginTransaction();
trans.replace(R.id.map, searchFrag);
mapShown = false;
trans.addToBackStack(null);
trans.commit();
}
if (whatFrag == MAP_FRAG) {
//switch to frag 2, ie mapFrag
if (lbServ != null) {
update.autoCenter(lbServ.getCurrentLatLng());
}
FragmentTransaction trans = fragMan.beginTransaction();
trans.replace(R.id.map, mapFrag);
mapShown = true;
trans.addToBackStack(null);
trans.commit();
}
}
}
The swapFrags() method is also called from my onTabSelected callback. I think, since I only have one fragment manager, and the same manager is calling all the addToBackStack() methods, it is more organized. When I open the app, select a new tab, then hit physical back key, it goes back to the previous tab, which is what you are after, no?
One thing I found difficult to learn with fragments is that all calls, keys, etc. go to the activity first. Push a button on the fragment, the activity that holds it gets the callback first (whether it uses it or not), then if there is a listener in the fragment it will get the call also, but fragments can't do really anything outside themselves, and fragment transactions involve objects outside the fragment.
I suppose you could set up an interface between fragment and activity, with a method like swapFrags() in the activity, where the fragment can ask to be swapped, that should do it too I think.