Setting RadioButton.setChecked with Multiple Fragments
Asked Answered
H

8

13

Hey I have a really stupid problem and I can't figure out why it is not working as expected. So I have a MultiFragment layout (each one has some different questions) using viewpager and FragmentStatePagerAdapter. When I open the screen that hosts all these fragments I am trying to restore the previous state (marking all answered questions) using a network call. However it seems that if my Fragment is not visible to the user it can't update the checked state of the radio button / checkboxes.

enter image description here

Does anybody know what I can do to achieve the wished behavior?

Cheers and thanks in advance!

 @Override
 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     super.onViewCreated(view, savedInstanceState);
     presenter.restoreAnswersFromPreviousSession(questionId);
}

@Override 
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisible()) {
        if (isVisibleToUser) {
            presenter.restoreAnswersFromPreviousSession(questionId);
            Log.d("Fit", "My Fragment is visible");
        } else {
                   Log.d("Fit", "My Fragment is not visible");
               }
    }
}

Here I restore the state (called after the request is successful)

previousReplies = repliesToRestore;
for (QualityReportReply reportReply : repliesToRestore) {
  int id = reportReply.id();
  switch (id) {
    case 201: {
      boolean tooThin = (boolean) reportReply.value();
      if (tooThin) {
        materialTooThinGroupYes.setChecked(true);
      } else {
        materialTooThinGroupNo.setChecked(true);
      }
      break;
    }
    case 202: {
      boolean tooThick = (boolean) reportReply.value();
      if (tooThick) {
        materialTooThickGroupYes.setChecked(true);
      } else {
        materialTooThickGroupNo.setChecked(true);
      }
      break;
    }
    case 203: {
      boolean drawingThreads = (boolean) reportReply.value();
      if (drawingThreads) {
        materialDrawThreadsGroupYes.setChecked(true);
      } else {
        materialDrawThreadsGroupNo.setChecked(true);
      }
      break;
    }
    case 204: {
      boolean flyingThreads = (boolean) reportReply.value();
      if (flyingThreads) {
        materialFlyingThreadsGroupYes.setChecked(true);
      } else {
        materialFlyingThreadsGroupNo.setChecked(true);
      }
      break;
    }
    case 205: {
      boolean knots = (boolean) reportReply.value();
      if (knots) {
        materialKnotsGroupYes.setChecked(true);
      } else {
        materialKnotsGroupNo.setChecked(true);
      }
      break;
    }
Hydraulic answered 6/4, 2017 at 7:25 Comment(8)
You have to wait to get fragment visible and then update check state of button/ checkboxes.Phosphorus
I added my code hereHydraulic
The Problem is that I need to do the request twice onViewCreated and on becoming visible, since I always want to show the latest state whenever the user enters a new Tab, however it doesn't seem to be triggered on the first fragment in the viewpager, and since I add them dynamically I (only 2 or 3) could be added at some point I have to do the network callHydraulic
So you says that onViewCreated is not called, or you are calling api on every swipe (which you don't want) ?Phosphorus
I added an image to my description, which shows the NOT wanted behaviour, the fields are somehow pre-selected but it's not the normal checked state I would need, this happens if I set setChecked() and the fragment where this is set is not visibleHydraulic
Leaving a comment, in order for you to be able to tag me.Ashleeashleigh
Try to set the state in model class. And keep in mind that your model class should be singleton class. Because of the singleton class you can get the state of checkbox. And yes don't forget to clear the status of the checkbox.Emmaline
Have you tried using a ViewModel created in the activity that holds the ViewPager to save the status of the Radio buttons?Gheber
T
1

UPDATE

Here is a working example, you can see it.

I think you are restoring state on the host Activity. Don't do this, do it in each Fragment. Let's say you have two fragments, Passform and Material, so instead of doing it in Activity, make a call inside fragment's onCreateView.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_passform, container, false);
    new FetchPassFormState().execute();
    return view;
}

Update views inside onPostExecute of FetchPassFormState or wherever you want, as per your need.

UPDATE

You can do one thing. You must be adding Fragments in your QuestionsViewPagerAdapter, try to setArguments of your Fragments there. Like,

Fragment fragment = new YourFragment();
Bundle bundle = new Bundle();
bundle.putString("your_key", "Your Data");
...
fragment.setArguments(bundle);

Now add this fragment. And inside Fragment's onCreateView, get this Bundle.

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_material_questions, container, false);
    ButterKnife.bind(this, view);
    getFragmentComponent().inject(this);
    presenter.setView(this);

    Bundle bundle = getArguments();
    String youdData = bundle.getString("your_key");
    ...
}

And then use this data to set views.

Trackless answered 14/4, 2017 at 6:4 Comment(6)
You said in your question When I open the screen that hosts all these fragments I am trying to restore the previous state (marking all answered questions) . That's why I suggested you this. Can you show me your host Activity and Fragment's onCreateView for more detail?Trackless
gist.github.com/dhartwich1991/…Hydraulic
gist.github.com/dhartwich1991/…Hydraulic
I added 2 gists with the stuff you wanted, I was desperate so I tried to do both, restore the state in the activity and pass on the values to the fragments, but my initial approach was to load the data inside the fragments (however i would do the same request 3 times which is why I moved loading to a central place in the activity)Hydraulic
Yea I think this doesn't solve my issue, since I was already doing that, with the difference that I was parsing the args in onCreateHydraulic
Here is a small project demonstrating what I said.Trackless
G
0

You need to use setOffscreenPageLimit

viewPager.setOffscreenPageLimit(<total_page_count>);

As documents says

Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.

Guthrie answered 10/4, 2017 at 7:34 Comment(2)
That doesn't help as well, I still only see a focussed state but they don't get "selected"Hydraulic
@Hydraulic This will helps for not re-creating fragment when swiping in ViewPager. You need to manually update your check box.Guthrie
C
0

I think one of the problems you have is to do the restore in onCreateView.

This is the way I update my fragments by calling webservices when the fragment becomes visible.

callToRestore() fires an async webservice call (using ion) after which I get a callback that populates the information of my fragment.

protected boolean restoreOnHold = false;

    @Override
        public void onResume() {
            super.onResume();
            if(restoreOnHold ){
                restoreOnHold = false;
                callToRestore();
            }
        }

    @Override
        public void setUserVisibleHint(boolean isVisible){
            super.setUserVisibleHint(isVisible);
            if (isVisible) {
                if(getView() != null){
                    restoreOnHold = false;

                    callToRestore();

                }else{
                    restoreOnHold = true;
                }

            }
        }
Cissoid answered 15/4, 2017 at 13:14 Comment(8)
I tried both (to do it in onCreateView and setUserVisibleHint) but it didn't show the wanted result for meHydraulic
Did you add the three components? the memeber variable restoreOnHold, onResume(), and setUserVisibleHint() ? If you set a breakpoint on setUserVisibleHint, is your ViewPager calling the method?Cissoid
I don't know about the restoreOnHold field you are describing, the setUserVisibleHint method was called, if the fragment was not the first fragment the user sees (the problem also never existed for the first one)Hydraulic
You have to add that member variable because it is a flag that is checked both in the onResume method and in the setUserVisibleHint method. It plays a part depending which method is called first. See everything I posted as answer, the three components.Cissoid
so you mean it could be a sort of concurrency issue? And I only should do my request once, so that I am not setting the state twice? Did I understand correctly?Hydraulic
When dealing with fragments you have concurrency issues. setUserVisibibleHint may be called before the fragment has finished setting up the view (getView() == null), in that case you flag the restore to happen in onResume().Cissoid
Okay, cool I will try to implement that, how do you trigger the initial loading? For the first visible fragment setUserVisibleHint will not be called.Hydraulic
Let us continue this discussion in chat.Cissoid
G
0

Since your fragments (which are VIEWS!) handle data, you got problems updating them at the right time. I hope this gives u a good start for more coding :-)

** edit: i want to point out my comment on putAllAnswersFromPreviousSessions(DataObject mObject)

    public class QuestionPagerAdapter extends FragmentStatePagerAdapter {

    private List<Fragment> mFragments;
    private Bundle mData = null;

    public QuestionPagerAdapter(FragmentManager fm) {
        super(fm);
        // u can add and remove fragments anytime during runtime with this List<Fragments>
        mFragments = new ArrayList<>();
    }

    /**
     * Everytime we show a new position, set your Bundle to your Fragments.
     * @param position
     * @return fragment with data;
     */
    @Override
    public Fragment getItem(int position) {
//        Note, that the method .setArguments(Bundle bundle) should only be called,
//        before the Fragments are attached. Therefore, we need to
//        @Override getItemPosition(Obj) to ensure, that your Fragments are redrawn, when selected;
        mFragments.get(position).setArguments(mData);
        return mFragments.get(position);
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    /**
     * Invoke this Method from your activity, when u get your data from the internet;
     * You could register an receiver inside your activity and when u get an result from your AsyncTask,
     * u could call
     *          mQuestionPagerAdapter.putAllAnswersFromPreviousSessions(result);
     *          mQuestionPagerAdapter.notifyDataSetChanged();
     * @param mObject a simple data object.
     */
    public void putAllAnswersFromPreviousSessions(DataObject mObject){
        Bundle dataObject = new Bundle();
        boolean[] mReceivedData = mObject.getAllAnswers();
        for (int i = 0; i < mReceivedData.length; i++){
            dataObject.putBoolean("answer_"+String.valueOf(i), mReceivedData[i]);
        }
        this.mData = dataObject;
    }
}

If u take care that your data is loaded before showing your fragments, you should not invoke mQuestionPagerAdapter.notifyDataSetChanged(). This Method forces the FragmentStatePagerAdapter to redraw your fragments and the position of the screen will get lost (same fragment will be shown though :-) )

Another neat trick i learned is retrieving your data anytime from your FragmentStatePagerAdapter:

     @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 
            super.onViewCreated(view, savedInstanceState);
            mViewPager = (ViewPager) getActivity().findViewById(R.id.view_pager);
            mStatePagerAdapter = (FragmentStatePagerAdapter) mViewPager.getAdapter();
        }

With both approaches, you should be able to store your data outside of your views (your fragments) and get/set it, when u need.

public class DataObject {

    private boolean[] mAnswersFromPreviousSessions;

    /**
     * This data-object is a suggestion.
     * Since u need to decide between three states (true / false / empty),
     * u should work with enums e.g.
     * @param answers your data..
     */
    public DataObject(boolean[] answers) {
        super();
        this.mAnswersFromPreviousSessions = answers;
    }

    /** get all answers, or **/
    public boolean[] getAllAnswers() {
        return mAnswersFromPreviousSessions;
    }

    /** just ask for a specific one **/
    public boolean getAnswerFor(int questionPosition){
        return mAnswersFromPreviousSessions[questionPosition];
    }
}
Gastrocnemius answered 21/4, 2017 at 23:27 Comment(0)
L
0

Try to save answers in one model class after network call while you host the main fragment and then use that model class in each fragment to fill up details.

Lamoureux answered 24/4, 2017 at 12:4 Comment(0)
A
0

First of all, When you create an activity do these steps. 1. Network call for all the data. 2. On successful result, seperate the data for different fragments. In your case 4 fragments i guess. 3. setup view pager and add fragments. 4. Send respective data to fragments using fragment.setArguments(bundle) and get data using getArguments. 5. Set result to your radio button/checkboxes on onCreateView of your fragment.

Activity onCreate:

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    // load the layout
    setContentView(R.layout.activity_layout);

    //  Your network call
    presenter.restoreAnswersFromPreviousSession(questionId);

}
@Override
public void onSuccess(Object result){

      // setup view your view pager and fragments here.
      // add restored replies to fragments.

      Fragment fragment = new FragmentOne();
      Bundle bundle = new Bundle();
      bundle.putExtra("restored_replies", replies);
      fragment.setArguments(bundle);

}

And get your restored replies on onCreateView for each fragment. Set checkboxes/radiobuttons according to data received.

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      View view = inflater.inflate(R.layout.fragment_questions, container, false);

      ...
      Bundle bundle = getArguments();
      Object data = bundle.getSerializableExtra("restored_replies"); 
      // Serialize object before passing as an argument.
      // set you check boxes and radio buttons according to the data received.
      ...

      return view;
}

Summary: Make network call on activity and send result to respective fragments to show.

Auramine answered 24/4, 2017 at 19:13 Comment(0)
H
0

I'm with the comment of Kirankumar Zinzuvadia

I had the exactly same problem with RadioButton in ViewPager. The reason was that setChecked() doesn't work when it is invisible.

So I called setChecked() on ViewPager.OnPageChangeListener.onPageSelected()

Java>

@Override 
public void onPageSelected(int position){
    if(position == 2){
        materialTooThinGroupYes.setChecked(true);
    }
}

Kotlin>

override fun onPageSelected(position: Int) {
    if(position == 2){
        materialTooThinGroupYes.isChecked = true;
    }
}
Histaminase answered 24/9, 2019 at 2:48 Comment(0)
R
-1

I have the same issue with my Quiz app. And I found one thing, that the android system is not updating the UI when the radioButton.setChecked(true); function is called. But it is reflected internally in the code (when you check it - checked or not).

To address this issue, you have to use the following function call of the widget RadioButton.

radioButton.setSelectedState(true); //This makes the ui selected. use false to make the ui not selected.

Here as for your code you have to change your function to

previousReplies = repliesToRestore;
for (QualityReportReply reportReply : repliesToRestore) {
  int id = reportReply.id();
  switch (id) {
    case 201: {
      boolean tooThin = (boolean) reportReply.value();
      if (tooThin) {
        materialTooThinGroupYes.setChecked(true); //Keep it if you want to get the values from the code
        materialTooThinGroupYes.setSelectedState(true); //To Change the UI
      } else {
        materialTooThinGroupNo.setChecked(true);
        materialTooThinGroupNo.setSelectedState(true); //To Change the UI
      }
      break;
    }
    case 202: {
      boolean tooThick = (boolean) reportReply.value();
      if (tooThick) {
        materialTooThickGroupYes.setChecked(true);
        materialTooThickGroupYes.setSelectedState(true); //To Change the UI
      } else {
        materialTooThickGroupNo.setChecked(true);
        materialTooThickGroupNo.setSelectedState(true); //To Change the UI
      }
      break;
    }
    case 203: {
      boolean drawingThreads = (boolean) reportReply.value();
      if (drawingThreads) {
        materialDrawThreadsGroupYes.setChecked(true);
        materialDrawThreadsGroupYes.setSelectedState(true); //To Change the UI
      } else {
        materialDrawThreadsGroupNo.setChecked(true);
        materialDrawThreadsGroupNo.setSelectedState(true); //To Change the UI
      }
      break;
    }
    case 204: {
      boolean flyingThreads = (boolean) reportReply.value();
      if (flyingThreads) {
        materialFlyingThreadsGroupYes.setChecked(true);
        materialFlyingThreadsGroupYes.setSelectedState(true); //To Change the UI
      } else {
        materialFlyingThreadsGroupNo.setChecked(true);
        materialFlyingThreadsGroupNo.setSelectedState(true); //To Change the UI
      }
      break;
    }
    case 205: {
      boolean knots = (boolean) reportReply.value();
      if (knots) {
        materialKnotsGroupYes.setChecked(true);
        materialKnotsGroupYes.setSelectedState(true); //To Change the UI
      } else {
        materialKnotsGroupNo.setChecked(true);
        materialKnotsGroupNO.setSelectedState(true); //To Change the UI
      }
      break;
    }
Rogerrogerio answered 24/4, 2017 at 9:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.