Race condition while using async task with viewpager - android

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.

Related

Refresh fragment from tab layout on click

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.

conflict between fragments inside a viewpager

whenever i swipe to move to the next fragment the code inside another fragment gets executed tho the layout is loaded properly and the code also but it also executes the code of another fragment
ex: 3 fragments A,b,c
when i swipe from fragment A to fragment b : fragment b layout and code are executed but also fragment c code
when i swipe from b to c , only the code and layout of fragment c ,so its executed properly
so the problem is if it isnt the last fragment it calls the code of next one
here is my main_activty code
public class Main2Activity extends AppCompatActivity {
private SectionsPagerAdapter mSectionsPagerAdapter;
private ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
home h1 = new home();
return h1;
case 1:
status st = new status();
return st;
case 2:
info info = new info();
return info;
case 3:
setting set = new setting();
return set;
}
return null;
}
#Override
public int getCount() {
return 4;
}
}
}
Your SectionsPagerAdapter is extending FragmentStatePagerAdapter, and its default behaviour is to preload at least one page for optimisation reasons. You can set the number of fragments it preloads using setOffscreenPageLimit but it has to be at least one.
You can use this method in your fragment and put in there something that you want executed only once the fragment becomes visible:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
}
}

Launching new Fragment Inside ViewPager

I'm on the verge of losing it. Anyone that could help me solve this issue will be showered with coding props for all of eternity.
I have a Tablayout containing 3 Tabs within my Main Activity. Each tab contains their own Fragment(Tasks,Calendar,Contacts). There is a FAB button in the Main Activity that lives on top of all the Fragments and performs a certain action base on what Tab I'm currently in.
My main issue is that, when I'm in Tab1, and the Fab is clicked, I need to replace the Tasks Fragment with a different Fragment called AddContact.
I've been looking up how to solve this for hours and finally settled on an earlier answer from this stack post
The issue is, I'm not quite grasping how they were able to do it.
My understanding is that the swapping of Fragments should be managed by the getItem method within the View Pager Adapter. And that there should be a listener that exists between the ViewPagerAdapter and the buttonClicked action in my Main Activity that tells it to execute.
The problem is that I can't make this link. I've tried a number of things but with no luck. Below I included my MainActivity Class and my ViewPagerAdapter Class. If someone could please explain to me how I could swap out my Tasks Fragment with my AddContacts Fragment in the ViewPager, I would greatly appreciate it
Main Activity:
public class MainActivity extends AppCompatActivity {
private CollapsingToolbarLayout collapsingToolbarLayout;
private ViewPager viewPager;
private ArrayList<Fragment> fragments = new ArrayList<>();
private ArrayList<String> titles = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
collapsingToolbarLayout = (CollapsingToolbarLayout)findViewById(R.id.collapsing_toolbar);
setSupportActionBar(toolbar);
fragments.addAll(Arrays.asList(new Tasks(),new Calendar(),new Contacts()));
titles.addAll(Arrays.asList(getResources().getString(R.string.fragment_task_title),
getResources().getString(R.string.fragment_cal_title),getResources().getString(R.string.fragment_contacts_title)));
TabLayout tabLayout = (TabLayout)findViewById(R.id.tabs);
final FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);
viewPager = (ViewPager)findViewById(R.id.container);
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(),fragments,titles);
viewPager.setAdapter(viewPagerAdapter);
viewPager.setCurrentItem(1);
tabLayout.setupWithViewPager(viewPager);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
fabAction();
}
});
}
public void fabAction(){
int current = viewPager.getCurrentItem();
switch (current){
case 0:{
//Need to indicate Fragment Swap here somehow. Through Listener?
break;
}
}
}}
ViewPagerAdapter Class:
public class ViewPagerAdapter extends FragmentPagerAdapter {
ArrayList<Fragment> fragments;
ArrayList<String> titles;
private Fragment mFragmentAtPos0;
public ViewPagerAdapter(FragmentManager fm, ArrayList<Fragment> fragments, ArrayList<String>titles) {
super(fm);
this.fragments = fragments;
this.titles = titles;
}
#Override
public Fragment getItem(int position) {
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
//HANDLES THE CHANGING OF FRAGMENTS IN HERE USING THE LISTENER BELOW? BUT I'M NOT GRASPING THE LOGIC
//ON HOW TO IMPLEMENT THIS.
}
}
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
//GOT FROM LESSON - ALLOWS VIEWPAGER TO CHANGE IT'S VIEW AND ATTACH NEW FRAGMENT WHEN LAUNCHED
#Override
public int getItemPosition(Object object)
{
if (object instanceof Tasks && mFragmentAtPos0 instanceof AddContact)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
//GOT FROM LESSON. A LISTENER BUT I'M NOT SURE WHERE AND HOW TO IMPLEMENT IT. WHERE IN THE MAIN ACTIVITY SHOULD THIS
//GO? HOW DOES IT COMMUNICATE BACK TO THE ADAPTER?
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}
The post or lesson that Im referring is also from 5 years ago so I don't know if there's a simpler way to implement this, in which case I would love to hear it. Thanks so much.

Send data from activity to fragment to update a list

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);

ViewPagerIndicator - How to avoid reload data in fragment each time returned

I am using Jake Wharton's ViewPagerIndicator to make a swipeable tab. Currently i can make it works in my app, with 4 tabs in it. But apparently each fragment is re-create when i change page/tab. For example, if I am in tab A, change to tab C, and back to tab A, the A tab's fragment is recreate again, not just bring the old one to front of the page.
I try to follow example of the library. Here is how my code looks like :
ProfileViewPagerActivity
private static final String[] TAB_TITLES = new String[] { "Info",
"Personal Event", "Favorite", "Attending" };
TestFragmentAdapter mAdapter;
ViewPager mPager;
PageIndicator mIndicator;
int action;
User user;
#Override
protected void onCreate(Bundle arg0) {
// TODO Auto-generated method stub
super.onCreate(arg0);
setContentView(R.layout.simple_tabs);
action = getIntent().getIntExtra(Constants.LOAD_WHAT, Constants.LOAD_MY_PROFILE);
user = (User) getIntent().getSerializableExtra(Constants.USER);
mAdapter = new TestFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TabPageIndicator) findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
and here is the adapter as a class inside ProfileViewPagerActivity
TestFragmentAdapter
private int mCount = TAB_TITLES.length;
public TestFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return UserInfoFragment.newInstance(action,user);
case 1:
return EventListFragment.newInstance(user.getUsername(), Constants.LIST_MINE, action);
case 2:
return EventListFragment.newInstance(user.getUsername(), Constants.LIST_FAVORITE, action);
case 3:
return EventListFragment.newInstance(user.getUsername(), Constants.LIST_ATTENDED, action);
}
return null;
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return TAB_TITLES[position].toUpperCase(Locale.US);
}
Using codes above, the fragment re-create itself each time I change tab. I try to make a different approach, such as calling newInstance() method for each fragment inside TestFragmentAdapter constructor, and then just call the fragment object i already have in the getItem method, and it still not work.
Another solution i already tried is to saveInstanceState in each fragment, but not works too. Am i missing something? Thanks in advance.
EDIT
I'm sorry, my mistake, i guess it's not "reinstantiated fragment" related question. After doing more debugging, i found that only data being reloaded again if the fragment showed in FragmentAdapter. In my fragment, i load data from web server in the OnCreateView method. From the android docs, i guess i should load data that I need in OnCreate method to solve this problem, am i right? thanks.
From Android developers website on FragmentPagerAdapter:The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible.
I believe android has its own mechanism to preserve the memory. If you are not convinced, you can always declare your fragments first:
private int mCount = TAB_TITLES.length;
private UserInfoFragment fragment0; // first fragment
private EventListFragment [] fragmentAt; // fragment 2,3,4
#Override
protected void onCreate(Bundle arg0) {
fragmentAt = new EventListFragment[mCount-1];
...
}
Then in your TestFragmentAdapter:
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if(fragment0 == null) {
fragment0 = UserInfoFragment.newInstance(action,user);
}
return fragment0;
case 1:
case 2:
case 3:
if(fragmentAt[position-1] == null) {
fragmentAt[position-1] = EventListFragment.newInstance(user.getUsername(), Constants.LIST_ATTENDED, action);
}
return fragmentAt[position-1];
}
}
This way, the fragments are guaranteed to be created only once.
You could try to make your TestFragmentAdapter extends FragmentPagerAdapter.

Categories

Resources