Android TabLayout Fragment Only Updates After Switching to Specific Fragment

Background

I’m having an issue involving updates to a Fragment in an Android project. The app rolls dice for D&D players. The project employs an API from Random.Org to provide for greater entropy in random number generation compared to the Java Math.random method.

The app displays a Tab Layout with a View Pager which switches between three tabs – one to roll a single die, one to roll a set of dice, and one to show the roll history and log info. Roll Die is in position 0, Roll Set is in position 1, and History is in position 2. View Pager uses a FragmentPagerAdapter.

History contains two TextViews which I use to display roll results and logging data to ensure connection to the API went well. Data is passed from the RollDie and RollSet fragments to the Main Activity by two interfaces. The interface methods then send the data to the FragmentPagerAdapter, which in turn passes two String ArrayLists to the History fragment via bundle.

The Problem

My problem is the History tab only seems to update after the single die roll tab is loaded. When I roll a single die then check the history page, the relevant TextViews display as they should. When I roll a set however, the history page doesn’t update until I return to the single die roll tab.

Any help with this issue would be greatly appreciated. Relevant code posted below.

Code from the API can be found on Github. I have also left out two classes I wrote called Die and DieSet – these work well and I figured this is already long enough. I’m happy to post those classes if anyone thinks it will help.

Code related to the API key has been omitted along with some IDE generated comments.

Main Activity

public class MainActivity extends AppCompatActivity implements DieRollFragment.RollFragmentListener, DieSetFragment.SetFragmentListener{

    private static String[] key;
    TabLayout tabLayout;
    ViewPager viewPager;
    PagerAdapter pagerAdapter;

    protected void onStart() {
        super.onStart();
        userKeyDialog();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tabLayout = findViewById(R.id.navTabs);
        viewPager = findViewById(R.id.viewPager);
        pagerAdapter = new PagerAdapter(getSupportFragmentManager(), BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, tabLayout.getTabCount());
        viewPager.setAdapter(pagerAdapter);
        tabLayout.setupWithViewPager(viewPager);
    }

    private void userKeyDialog(){
        //OMITTED FOR PRIVACY REASONS
    }

    public static String getKey() {
        //OMITTED FOR PRIVACY REASONS
    }


    //DieRollFragment Interface Methods
    @Override
    public void rollResultsUpdate(String input) {
        pagerAdapter.setResults(input);
    }

    @Override
    public void rollLogsUpdate(String input) {
        pagerAdapter.setLogs(input);
    }
    
    //DieSetFragment Interface Methods
    @Override
    public void setResultsUpdate(String input) {
        pagerAdapter.setResults(input);
    }

    @Override
    public void setLogsUpdate(String input) {
        pagerAdapter.setLogs(input);
    }
}

FragmentPagerAdapter

public class PagerAdapter extends FragmentPagerAdapter {

    int numOfTabs;
    ArrayList<String> results;
    ArrayList<String> logs;

    public PagerAdapter(FragmentManager fm, int behavior, int numOfTabs) {
        super(fm, behavior);
        this.numOfTabs = numOfTabs;
        results = new ArrayList<>();
        logs = new ArrayList<>();
    }


    @NonNull
    @Override
    public Fragment getItem(int position) {
        switch(position){
            case(1):
                return DieSetFragment.newInstance(getKey());
            case(2):
                return RollHistoryFragment.newInstance(results, logs);
            case(0):
            default:
                return DieRollFragment.newInstance(getKey());
        }
    }

    @Override
    public CharSequence getPageTitle(int position){
        String title;
        switch(position){
            case(1):
                title = "Roll Set";
                return title;
            case(2):
                title = "Results";
                return title;
            case(0):
            default:
                title = "Roll Die";
                return title;
        }

    @Override
    public int getCount() {
        return 3;
    }

    public void setResults(String input) {
        results.add(input);
    }

    public void setLogs(String input) {
        logs.add(input);
    }
}

RollHistoryFragment

public class RollHistoryFragment extends Fragment{

    TextView results, logs;
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    ArrayList<String> inputResults, inputLogs;
    Button clearLogs;

    public RollHistoryFragment() {
        // Required empty public constructor
    }

    public static RollHistoryFragment newInstance(ArrayList<String> inputResults, ArrayList<String> inputLogs) {
        RollHistoryFragment fragment = new RollHistoryFragment();
        Bundle args = new Bundle();
        args.putStringArrayList(ARG_PARAM1, inputResults);
        args.putStringArrayList(ARG_PARAM2, inputLogs);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            inputResults = getArguments().getStringArrayList(ARG_PARAM1);
            inputLogs = getArguments().getStringArrayList(ARG_PARAM2);
        }
    }

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

        results = view.findViewById(textViewResults);
        results.setMovementMethod(new ScrollingMovementMethod());
        logs = view.findViewById(textViewLogs);
        logs.setMovementMethod(new ScrollingMovementMethod());

        clearLogs = view.findViewById(buttonClear);
        clearLogs.setOnClickListener(view1 -> {
            results.setText("");
            logs.setText("");
        });

        updateResults(inputResults);
        updateLogs(inputLogs);
        return view;
    }

    public void updateResults(ArrayList<String> input){
        for (String s : input) results.append(s+"\n");
    }

    public void updateLogs (ArrayList<String> input){
        for (String s : input) logs.append(s+"\n");
    }
}

DieRollFragment

public class DieRollFragment extends Fragment {

    private static final String ARG_PARAM1 = "param1";
    private String key;
    private RollFragmentListener listener;

    public DieRollFragment() {
        // Required empty public constructor
    }

    public static DieRollFragment newInstance(String key){
        DieRollFragment fragment = new DieRollFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, key);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            key = getArguments().getString(ARG_PARAM1);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_die_roll, container, false);
        ImageButton rollD4 = view.findViewById(R.id.rollD4);
        ImageButton rollD6 = view.findViewById(R.id.rollD6);
        ImageButton rollD8 = view.findViewById(R.id.rollD8);
        ImageButton rollD10 = view.findViewById(R.id.rollD10);
        ImageButton rollD12 = view.findViewById(R.id.rollD12);
        ImageButton rollD20 = view.findViewById(R.id.rollD20);
        ImageButton rollD100 = view.findViewById(R.id.rollD100);

        rollD4.setOnClickListener(view1 -> {
            Die d4 = new Die(4, key);
            int result = d4.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d4.getLog());
        });

        rollD6.setOnClickListener(view12 -> {
            Die d6 = new Die(6, key);
            int result = d6.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d6.getLog());
        });

        rollD8.setOnClickListener(view13 -> {
            Die d8 = new Die(8, key);
            int result = d8.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d8.getLog());
        });

        rollD10.setOnClickListener(view14 -> {
            Die d10 = new Die(10, key);
            int result = d10.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d10.getLog());
        });

        rollD12.setOnClickListener(view15 -> {
            Die d12 = new Die(12, key);
            int result = d12.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d12.getLog());
        });

        rollD20.setOnClickListener(view16 -> {
            Die d20 = new Die(20, key);
            int result = d20.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d20.getLog());
        });

        rollD100.setOnClickListener(view17 -> {
            Die d100 = new Die(100, key);
            int result = d100.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d100.getLog());
        });
        return view;
    }

    public interface RollFragmentListener{
        void rollResultsUpdate(String input);
        void rollLogsUpdate(String input);
    }

    private void showAlert(int result){
        new AlertDialog.Builder(Objects.requireNonNull(getContext()))
                .setTitle("You rolled a: ")
                .setMessage(Integer.toString(result))
                .show();
    }

    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if(context instanceof RollFragmentListener){
            listener = (RollFragmentListener) getContext();
        } else throw new RuntimeException(context.toString() + " must implement RollFragmentListener");
    }
    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }
}

DieSetFragment

    public DieSetFragment() {
        // Required empty public constructor
    }

    public static DieSetFragment newInstance(String key) {
        DieSetFragment fragment = new DieSetFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, key);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_die_set, container, false);
        assert container != null;
        editNumD4 = view.findViewById(R.id.editNumD4);
        editNumD6 = view.findViewById(R.id.editNumD6);
        editNumD8 = view.findViewById(R.id.editNumD8);
        editNumD10 = view.findViewById(R.id.editNumD10);
        editNumD12 = view.findViewById(R.id.editNumD12);
        editNumD20 = view.findViewById(R.id.editNumD20);
        diceSetList = new ArrayList<>();

        ImageButton buttonRollSet = view.findViewById(R.id.buttonRollSet);
        buttonRollSet.setOnClickListener(view1 -> rollSet(mParam1));

        return view;
    }

    public interface SetFragmentListener{
        void setResultsUpdate(String input);
        void setLogsUpdate(String input);
    }

    public void rollSet(String key){
        int numD4 = Integer.parseInt(editNumD4.getText().toString());
        int numD6 = Integer.parseInt(editNumD6.getText().toString());
        int numD8 = Integer.parseInt(editNumD8.getText().toString());
        int numD10 = Integer.parseInt(editNumD10.getText().toString());
        int numD12 = Integer.parseInt(editNumD12.getText().toString());
        int numD20 = Integer.parseInt(editNumD20.getText().toString());

        for(int i = 0; i<numD4; i++){
            Die d4 = new Die(4,key);
            diceSetList.add(d4);
        }
        for(int i = 0; i<numD6; i++){
            Die d6 = new Die(6,key);
            diceSetList.add(d6);
        }
        for(int i = 0; i<numD8; i++){
            Die d8 = new Die(8,key);
            diceSetList.add(d8);
        }
        for(int i = 0; i<numD10; i++){
            Die d10 = new Die(10,key);
            diceSetList.add(d10);
        }
        for(int i = 0; i<numD12; i++){
            Die d12 = new Die(12,key);
            diceSetList.add(d12);
        }
        for(int i = 0; i<numD20; i++){
            Die d20 = new Die(20,key);
            diceSetList.add(d20);
        }

        DieSet dieSet = new DieSet(diceSetList,key);
        int result = dieSet.rollTotal();
        listener.setLogsUpdate(dieSet.getLOG());
        listener.setResultsUpdate("Set Roll = " + result);
        showAlert(result);
        diceSetList.clear();
    }

    public void showAlert(int result){
        new AlertDialog.Builder(Objects.requireNonNull(getContext()))
                .setTitle("You rolled a: ")
                .setMessage(Integer.toString(result))
                .show();
    }

    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if(context instanceof SetFragmentListener){
            listener = (SetFragmentListener) getContext();
        } else throw new RuntimeException(context.toString() + "must implement SetFragmentListener");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }
}

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

After some work I found a simple solution.

I removed the calls to my update methods from the History fragment’s onCreateView, and added onResume.

I then put an if statement in onResume which checks that the ArrayLists inputResults and inputLogs are not null, then calls updateResults and updateLogs. I then clear inputResults and inputLogs.

Altered code below:

 public void onResume() {
        super.onResume();

        if (inputResults != null && inputLogs != null){
            updateResults(inputResults);
            updateLogs(inputLogs);
        }

        inputResults.clear();
        inputLogs.clear();
    }

Everything works as intended now! I consider this question answered to my satisfaction, but I’m no Android ace so if anyone has advice on how I can improve this thing I’m open to suggestions!

A few other final notes: I switched from the deprecated ViewPager to ViewPager2. As such Page Adapter now extends FragmentStateAdapter. This doesn’t change much in terms of function but makes the MainActivity code look a bit neater.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply