1

The title might be a little confusing so I hope that I can express my problem correctly. So I'm working on a simple workout log app. I have fragment ActiveWorkout as in Image 1. When I press the pink add button I am directed to another fragment, which contains different exercises as a List, which is shown in Image 2. Then I am to select an exercise(with the onItemClickListener) and the title of that exercise should be sent back to the ActiveWorkoutFragment and added to the Active Workout, so it should be like Image 3. So the problem here is, I don't exactly know how to keep my Active Workout 'alive', it should be updated if I want to add another exercise and not be blank when I press the pink add button again, so in the end it should be something like in Image 4. I was thinking about sending data with a Bundle, which I also tried in the code but the more difficult part is updating the Workout list without deleting the previous added exercises. Btw the reason I am trying to do something like this is because the data is actually in Firebase Database, so in a sense I'm trying to retrieve data from the Workout database.

Images:

Image 1 Image 2 Image 3 Image 4

This is the Exercise List or the Catalog:

public class ExercisesFragment extends Fragment {

    private ListView lv;
    private FirebaseListAdapter adapter;
    private ArrayList<Exercise> exerciseList;
    private ArrayList<String> nameList;
    //private Adapter adapter;

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_exercises, container, false);
        lv = v.findViewById(R.id.lv);
        Query query = FirebaseDatabase.getInstance().getReference().child("exerciseList");
        FirebaseListOptions<ExerciseElement> options = new FirebaseListOptions.Builder<ExerciseElement>()
                .setLayout(R.layout.exercise)
                .setQuery(query, ExerciseElement.class)
                .build();

        adapter = new FirebaseListAdapter(options) {
            @Override
            protected void populateView(@NonNull View v, @NonNull Object model, int position) {
                TextView bodypart = v.findViewById(R.id.bodypart);
                TextView title = v.findViewById(R.id.title);

                ExerciseElement el = (ExerciseElement) model;
                bodypart.setText(el.getBodypart().toString());
                title.setText(el.getTitle().toString());


            }
        };
        lv.setAdapter(adapter);


        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                ExerciseElement el = (ExerciseElement) lv.getItemAtPosition(position);
                String item = el.getTitle();

                ActiveWorkoutFragment awf = new ActiveWorkoutFragment();
                Bundle args = new Bundle();
                args.putString("ExerciseTitle", item);
                awf.setArguments(args);

                getFragmentManager().beginTransaction().replace(R.id.nav_host_fragment, awf).commit();
                //Navigation.findNavController(v).navigate(R.id.activeWorkoutFragment);

            }
        });
        return v;
    }

    @Override
    public void onStart() {
        super.onStart();
        adapter.startListening();
    }

    @Override
    public void onStop() {
        super.onStop();
        adapter.stopListening();
    }
}

Then there is the ActiveWorkoutFragment, which is a little longer but the upper part is not the concern. The addNewExercise() method is being called when I click on the pink add button, so I was trying to retrieve the data somehow there.

public class ActiveWorkoutFragment extends Fragment {

    private Workout workout;
    private TextView emptyRecyclerView;
    private RecyclerView exerciseRecyclerView;
    private ExerciseRecyclerViewAdapter adapter;
    private WorkoutHistory workoutHistory;
    private FloatingActionButton fab;
    private FirebaseAuth mAuth;
    private DatabaseReference databaseWorkouts;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {



        View v =  inflater.inflate(R.layout.fragment_active_workout, container, false);
        fab = v.findViewById(R.id.fab);
        fab.setOnClickListener(this::addNewExercise);
        setHasOptionsMenu(true);

        workout = new Workout("WORKOUT");

        if(savedInstanceState != null) {
            workout = savedInstanceState.getParcelable("key");
        }

        emptyRecyclerView = v.findViewById(R.id.empty_recycler_view);
        //buildRecyclerView(workout);
        exerciseRecyclerView = v.findViewById(R.id.recycler_view_exercise);

        // improves performance if size of RecyclerView content is fixed
        // taken from developer.android.com
        exerciseRecyclerView.setHasFixedSize(true);

        // use a linear layout manager for RecyclerView
        LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext());
        exerciseRecyclerView.setLayoutManager(layoutManager);

        // add divider
        RecyclerView.ItemDecoration itemDecoration = new
                DividerItemDecoration(this.getContext(), DividerItemDecoration.VERTICAL);
        exerciseRecyclerView.addItemDecoration(itemDecoration);

        // Create adapter and set its data set to workout
        adapter = new ExerciseRecyclerViewAdapter(workout, this);

        // Set up swipe to dismiss and ability to move RecyclerView items around

        // Create callback object for ItemTouchHelper
        ItemTouchHelper.Callback callback = new CustomItemTouchHelperCallback(adapter);

        // Implement object created above
        ItemTouchHelper touchHelper = new ItemTouchHelper(callback);

        touchHelper.attachToRecyclerView(exerciseRecyclerView);

        if (adapter.getItemCount() == 0)
        {
            showEmptyRecyclerViewText();
        }
        else
        {
            exerciseRecyclerView.setAdapter(adapter);
        }

        return v;
    }

    // Adds save button (check mark) to action bar
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.active_workout_action_bar, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_save: {

                List<Exercise> exercises = workout.getExercises();

                mAuth = FirebaseAuth.getInstance();
                String user_id = mAuth.getCurrentUser().getUid();


                databaseWorkouts = FirebaseDatabase.getInstance().getReference("Workouts").child(user_id);
                String id = databaseWorkouts.push().getKey();

                workout = new Workout("abc", user_id, exercises);
                databaseWorkouts.child(id).setValue(workout);


                Toast.makeText(getContext(), "Workout saved", Toast.LENGTH_SHORT).show();

                return true;
            }
            case R.id.action_delete: {
                exerciseRecyclerView.requestFocus();

                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                builder.setTitle("Confirm deletion").setMessage("Are you sure you want to delete" +
                        " this workout?");

                builder.setPositiveButton(android.R.string.yes, (dialog, which) -> {
                    try {
                        workoutHistory.removeWorkout(workout);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    Toast.makeText(getContext(), "Workout deleted", Toast.LENGTH_SHORT).show();
                });
                builder.setNegativeButton(android.R.string.no, (dialog, which) -> {
                    dialog.dismiss();
                });

                builder.show();
                return true;
            }
            default:
                // unrecognized button pressed
                return super.onOptionsItemSelected(item);
        }
    }

    public void showEmptyRecyclerViewText()
    {
        emptyRecyclerView.setVisibility(View.VISIBLE);
    }

    public void addNewExercise(View view) {

        Bundle bundle = getArguments();
        String value = "";
        if(bundle != null) {
            value = bundle.getString("ExerciseTitle");
        }
        System.out.println("lala");
        System.out.println(value);

        Exercise newExercise = new Exercise(value, -1, -1);
        if (exerciseRecyclerView.getAdapter() == null) {
            exerciseRecyclerView.setAdapter(adapter);
        }


        emptyRecyclerView.setVisibility(View.INVISIBLE);

        workout.addExercise(newExercise);

        adapter.notifyItemInserted(workout.getExercises().size() - 1);
    }

    private void hideKeyboard(Context context, View view) {
        InputMethodManager imm = (InputMethodManager) context.getSystemService
                (Activity.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable("key", workout);
    }
}
cheva
  • 25
  • 6
  • so the main issue is that when you select from image2 to image1 .. image1 is not updated in other words its recreated? – Faisal Naseer Jan 29 '20 at 18:10
  • And please avoid ListView instead go with RecyclerView – Faisal Naseer Jan 29 '20 at 18:11
  • Yeah actually its about that. I mean data transfer between fragments is the one thing here, but like you said the main problem is that image1 must be updated without being recreated again and again when i click on the pink add button. Also should I be using Bundle here to transfer the data? I actually transfer only Strings, namely the title of the exercises. – cheva Jan 29 '20 at 18:14
  • What is the disadvantage of ListView, I am quite new to the concept. – cheva Jan 29 '20 at 18:15
  • Recyclerview enforces the recycling of views by using the ViewHolder pattern. so it adds to the performance and recommended by community instead oof ListView which is outdated – Faisal Naseer Jan 29 '20 at 18:18
  • It doesn't work like so the flow is not correct... right now what I have understood is that from ActiveWorkoutFragment you click on the button on addNewExercise and it will take you to ExercisesFragment form where you select any exercise and it will passed back to ActiveWorkoutFragment? – Faisal Naseer Jan 29 '20 at 18:33
  • there may be several scenerios for that you can use Activity to maintain the list of of exercises you got from ExercisesFragment . and then when reloading ActiveWorkoutFragment you can use it to populate again which is just a workaround. but its not recommended or you can maintain the BackStack of fragments and use pop listener against fragments in backstack to determine the Loaded Fragment. and perform operations accordingly but in both cases you can pass data using activity. Its like maintaining the list in global scope – Faisal Naseer Jan 29 '20 at 18:39
  • Yeah, I noticed right now I deleted a few lines where I direct the app to the ExerciseFragment. But yeah like you said, the idea is that I click on the pink FloatingActionButton, which leads to the function addNewExercise. Then we should be directed to ExerciseFragment, select one exercise and the title of this exercise should be passed back to ActiveWorkoutFragment. And Image 2 should be the result of it. – cheva Jan 29 '20 at 18:40
  • have a look at https://developer.android.com/training/basics/fragments/communicating – Faisal Naseer Jan 29 '20 at 18:44
  • Actually I thought about that with the global scope too. What I understand is that the list would be generated again and again when I call ActiveWorkoutFragment, but of course as an updated form of itself. – cheva Jan 29 '20 at 18:46
  • no you dont have to generate it again and again but you can use the bridging pattern.. have a look and let me know if it works – Faisal Naseer Jan 29 '20 at 18:47
  • https://stackoverflow.com/questions/13700798/basic-communication-between-two-fragments – Faisal Naseer Jan 29 '20 at 18:47

1 Answers1

0

Try this as a base for your project. add views to it and other necessary methods however it will provide you with the flow of module

//contract to update Framgents
public interface FragUpdater {
    public void updateExerciseFrag(Exercise exercise);
}



public class ExerciseActiity extends AppCompatActivity implements FragUpdater {
    FragmentManager fm;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_exercise_actiity);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                gotoActiveWorkoutFragment();
            }
        });
        fm = getSupportFragmentManager();

        gotoExercisesFragment();
    }

    private void gotoActiveWorkoutFragment() {
        ActiveWorkoutFragment activeWorkoutFragment = new ActiveWorkoutFragment();
        Bundle bundle = new Bundle();
        activeWorkoutFragment.setArguments(bundle);
        fm.beginTransaction().add(R.id.content_frame, activeWorkoutFragment, "ExerciseSelectionFrag").addToBackStack(null).commit();
    }

    private void gotoExercisesFragment() {
        ExercisesFragment exercisesFragment = new ExercisesFragment();
        Bundle bundle = new Bundle();
        exercisesFragment.setArguments(bundle);
        fm.beginTransaction().replace(R.id.content_frame, exercisesFragment, "ExerciseDisplayFrag").commit();
    }

    @Override
    public void updateExerciseFrag(Exercise exercise) {
        // Get Fragment ExercisesFragment
        ExercisesFragment frag = (ExercisesFragment)
        fm.findFragmentByTag("ExerciseDisplayFrag");
        if (frag == null) {
            return;
        }
        frag.updateList(exercise);
    }


}



public class ExercisesFragment extends Fragment {

    //here update your list in ExerciseFragment
    public void updateList(Exercise exercise) {}
}


public class ActiveWorkoutFragment extends Fragment {
    FragUpdater fragUpdater;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fragUpdater = (ExerciseActiity) getActivity();
    }

    private void selectListItem(Exercise exercise) {
        fragUpdater.updateExerciseFrag(exercise);
        getActivity().getSupportFragmentManager().popBackStack();


    }
}
Faisal Naseer
  • 4,110
  • 1
  • 37
  • 55