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);
Related
I have a tab layout with 2 fragment and I have a toolbar with different options that belong to the activity which hosts the view pager.
When I click on a toolbar option, it clean the data from DB that are used in the fragment and then it should refresh it to apply changes at screen.
But I can't find a way to refresh the fragment from the activity.
Here is the fragment adapter :
public class FragmentAdapter extends FragmentPagerAdapter
{
private final List<Fragment> lstFragment = new ArrayList<>();
private final List<String> lstTitles = new ArrayList<>();
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return lstFragment.get(i);
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
return lstTitles.get(position);
}
#Override
public int getCount() {
return lstTitles.size();
}
public void AddFragment (Fragment fragment , String title)
{
lstFragment.add(fragment);
lstTitles.add(title);
}
}
Here is the way I call it in the fragments in main :
FragAdapter = new FragmentAdapter(getSupportFragmentManager());
((FragmentAdapter) FragAdapter).AddFragment(new FragmentProgramme(),"Programmes");
((FragmentAdapter) FragAdapter).AddFragment(new FragmentPlanning(),"Agenda"); //FRAGMENT I WANT TO REFRESH
mviewpager.setAdapter(FragAdapter);
mtablayout = (TabLayout) findViewById(R.id.main_tabs);
mtablayout.setupWithViewPager(mviewpager);
In the main, I have the option on the toolbar and I would like to do something like this :
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int pos = item.getItemId();
switch (pos)
{
DatabaseAgenda.getInstance(this).dropDB(); //CLEAN DB
// I would like to find a function like this : ((FragmentAdapter) FragAdapter).refresh(FragmentPlanning, "Agenda");
break;
}
return super.onOptionsItemSelected(item);
}
The point is to simply destroy the fragment because all the data must be cleaned from screen and then recreate it so the users can add new data to it
I hope this is clear, thank you for your hlelp
I have found a lot answers but never what I was really looking for
You can override the method setUserVisibleHint inside fragments to detect when it is visible. Data can be refreshed inside this method. Selecting the items of pageradapter programatically triggers setUserVisibleHint of present fragment. You would not need destroying fragments each time.
I have a screen that uses viewpager which contains two fragments A and B and data in fragments are loaded using async task. My MainActivity class contains the following code in onCreate method:
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
pager = (ViewPager) findViewById(R.id.pager);
adapter = new MyPagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
tabLayout.setupWithViewPager(pager);
//Async task to get data from server and update the fragments
runGetDataTask(true);
/**
* Adapter class for View pager
*/
public class MyPagerAdapter extends FragmentPagerAdapter {
private final String[] TITLES = {"A",
"B"};
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
return TITLES[position];
}
#Override
public int getCount() {
return TITLES.length;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
iFragment = A.getInstance();
if (Utility.IS_DEBUG)
Log.i(LOG_TAG,"A fragment created");
return iFragment;
case 1:
dFragment = B.getInstance();
if (Utility.IS_DEBUG)
Log.i(LOG_TAG,"B fragment created");
return dFragment;
}
return null;
}
}
The problem is that sometimes Async task completes its execution before fragments are initialized and when it try to access iFragment and dFragment to update data its has got from server I got nullpointerexception because fragments have not been initialized.
But this happens sometimes, I don't know what am I doing wrong here. Could someone please help me fix this issue?
Thanks
I think a possibile workaround is to make sure that the ViewPager is initialised after the AsyncTask completes the its job. This can be achieved by creating a listener like:
public interface DataListener{
public void onDataReceived();
}
and provide it to the AsyncTask (your activity must implement this interface). When the AsyncTask completes its work, then the listener is called and the control will be transferred back to the Activity that can initialise the UI.
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.
On my screen I have 2 containers - left for 3 fragments in view pager, right for single fragment. From this on the right I increment some temp variable and through inteface I pass this variable to fragments. I switch fragments in view pager and everything seems to act all right. But, there are two problems.
First is that when I start my app and try to actualize temp variable in third fragment in view pager there is NPE. And of course it should be, because at this point third fragment is not initialized (view pager only initialize actual fragment and the ones on his sides, to be able to slide between fragments). How can I initialize third fragment at the begining? I thought I can store data temporarily in second fragment and when second is initialized I can pass the data to third, but it seems not good solution.
Second question is when I initialize all fragments by sliding on it and I come back to first fragment view of third is gone (but it's initialized). When I actualize temp variable and slide to third fragment I can see this variable but after +- one second. Can someone explain me why? Without this knowledge I can't solve this problem :/
Here you have some code of mine:
public class ThirdFragment extends Fragment {
View thisView;
TextView tempTextView;
String iterator;
public AchievFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iterator = "Start";
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
thisView = inflater.inflate(R.layout.fragment_third, container, false);
tempTextView= (TextView) thisView.findViewById(R.id.tempTextView);
tempTextView.setText(iterator);
return thisView;
}
public void getVariable(String text) {
iterator = text;
tempTextView.setText(iterator);
}
So it's third fragment class. At the beginning I initialize iterator (it's this temp variable) as a global variable to avoid npe after initializing fragments. And in onCreateView i set the view of TextView to value of this variable. Should work fine, but this +-1 sec offset makes me sad.
I think this sample of code should be enough, the rest is standard I suppose, but if anyone will be really interested I'll past the rest.
I really hope you will understand what I just wrote.
Here is the main (hosting) activity code:
public class MainActivity extends AppCompatActivity implements OnFragmentInteractionListener {
ViewPager mViewPager;
FragmentPageAdapter ft;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.pager_container);
ft = new FragmentPageAdapter(getSupportFragmentManager());
mViewPager.setAdapter(ft);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
RightFragment gameFragment = new RightFragment();
ft.replace(R.id.rightFragment_container, rightFragment,"RightFragment");
ft.commit();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
#Override
public void OnUpdateFirst(String text) {
FirstFragment firstFragment = (FirstFragment) ft.instantiateItem(mViewPager,0);
firstFragment.getVariable(text);
}
#Override
public void OnUpdateSecond(String text) {
SecondFragment secondFragment = (SecondFragment) ft.instantiateItem(mViewPager,1);
secondFragment.getVariable(text);
}
#Override
public void OnUpdateThird(String text) {
ThirdFragment thirdFragment = (ThirdFragment) ft.instantiateItem(mViewPager,2);
thirdFragment.getVariable(text);
}
}
Fragment Page Adapter code:
public class FragmentPageAdapter extends FragmentPagerAdapter {
public FragmentPageAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int arg0) {
switch (arg0)
{
case 0:
return new FirstFragment();
case 1:
return new SecondFragment();
case 2:
return new ThirdFragment();
default:
break;
}
return null;
}
#Override
public int getCount() {
return 3;
}
Expection when I try to actualize variable on third fragment before I initialize it (in example right after I start app)
06-14 16:47:25.621 27246-27246/com.package.app E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
at com.package.app.ThirdFragment.getVariable(ThirdFragment.java:37)
at com.package.app.MainActivity.OnUpdateThird(MainActivity.java:59)
at com.package.app.RightFragment$3.onClick(RightFragment.java:57)
at android.view.View.performClick(View.java:4101)
at android.view.View$PerformClick.run(View.java:17082)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4940)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:798)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:565)
at dalvik.system.NativeStart.main(Native Method)
As to your first question you can call setOffscreenPageLimit(2) method on your ViewPager right after initializing it. By default it is set to 1 that means that adapter creates one page which is on the screen right now and one that is next.
I understand that this is quite a common issue, and I have referred to many different other questions but I still can't get this to work.
My activity implements a view pager with two tabs and in each tab is a listview. I have a adapter for my view pager which links the two fragments and in each fragment, a adapter to link the data to the listview.
In my activity menu, I have a menu which creates an edittext in an alertdialog for me to input new fields into one of the listview in one of the fragment.
My activity (contains a viewpager)
protected void onCreate(Bundle savedInstanceState)
{
...
subAdapter = new SubAdapter(getSupportFragmentManager(), data);
((ViewPager) findViewById(R.id.viewPager)).setAdapter(subGroupAdapter);
}
My viewpager adapter
public class SubAdapter extends FragmentPagerAdapter
{
public SubGroupAdapter(FragmentManager fm, data data)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
Bundle bundle = new Bundle();
bundle.putString("data", data);
switch (position)
{
case 0:
Fragment1 frag1 = new Fragment1();
frag1.setArguments(bundle);
return frag1;
case 1:
Fragment2 frag2 = new Fragment2();
frag2.setArguments(bundle);
return frag2;
}
return null;
}//some other methods below
Fragment1 / Fragment2 (both fragments have a listview)
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
frag1Adapter = new frag1Adapter(this, data);
((ListView) getView().findViewById(R.id.listView)).setAdapter(frag1Adapter);
}
Custom listview adapter for both listviews in both fragments
public class ExpenseAdapter extends BaseAdapter implements OnClickListener
{ ... }
As I mentioned earlier, I can input a new entry into either listview from the activity action bar button. However, the listview does not get updated and I can't reference the listview adapter to call notifydatasetchanged() from the activity.
What is the best way I can proceed from here onwards? thank you so much!
I have tried exploring using interfaces, tags but can't get it to work currently.
What you should do is create a public method for your Fragment1 and Fragment2 like so:
define in your Activity:
Fragment1 frag1;
Fragment2 frag2;
then your ViewPager:
public class SubAdapter extends FragmentPagerAdapter
{
public SubGroupAdapter(FragmentManager fm, data data)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
Bundle bundle = new Bundle();
bundle.putString("data", data);
switch (position)
{
case 0:
frag1 = new Fragment1();
frag1.setArguments(bundle);
return frag1;
case 1:
frag2 = new Fragment2();
frag2.setArguments(bundle);
return frag2;
}
return null;
}
}
Put this method in Fragment1.java :
public void updateFragment1ListView(){
if(adapter != null){
adapter.notifyDataSetChanged();
}
}
and from your activity call:
if(frag1 != null){
frag1.updateFragment1ListView();
}
obviously change the names for your adapter if its not called adapter...
Just do the same for Fragment2.java as well
I would recommend creating an interface. Here's an Example:
public interface UpdateListFragmentInterface {
void updateList();
}
Then in the Activity, add the delegate as a property:
UpdateListFragmentInterface yourDelegate;
Wherever you create the Fragment within the Activity, assign the Fragment as the delegate:
// Set up and create your fragment.
(if yourFragment instanceof UpdateListFragmentInterface) {
yourDelegate = yourFragment;
}
Then in the Fragment, implement the interface:
public class YourListFragment extends android.support.v4.app.ListFragment implements UpdateListFragmentInterface {
// Constructors and other methods for the ListFragment up here.
// Override the interface method
public void updateList() {
//Update the list here.
}
}
Then finally, from within your Activity, when you need to update the list, simply call the delegate:
yourDelegate.updateList();
EDIT:
Sorry, I think you actually need to create a public method in your activity that is something like:
public void setUpdateListFragmentDelegate(UpdateListFragmentDelegate delegate) {
this.delegate = delegate
}
Then in the on attach of the List Fragment you will assign it there:
if (context instance of YourActivity) {
((YourActivity)context.setUpdateListFragmentDelegate(this);
}