Marian Radu
Marian Radu

Reputation: 1

Fragments with Mvvm and observer pattern are not working

I am new to android studio and I am dealing with this problem for few hours and I cannot figure it out. Hear me out. I am making and android application and inside of a main activity I have multiple fragments navigable with a bottom navigation bar. In one of the fragments, I have a recycler view which contains one or multiple items. `

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

The item:

<TextView
        android:id="@+id/activeWorkoutNameTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Workout Name"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

When an item is clicked, it should open a new fragment.

I made it work but the problem is that i am not using Mvvm arhitecture with oberserver pattern. Because of this, if I rotate the phone or navigate back and then click on the same item in the list, the state of the fragment is lost and any data that I write in there is lost. How I made it work:

public class ActiveWorkoutsDetailFragment extends Fragment {

    private static final String ARG_WORKOUT = "workout";
    private WorkoutFromAPI workout;
    private LinearLayout fieldsContainer;
    private int setCount = 0;

    public static ActiveWorkoutsDetailFragment newInstance(WorkoutFromAPI workout) {
        ActiveWorkoutsDetailFragment fragment = new ActiveWorkoutsDetailFragment();
        Bundle args = new Bundle();
        args.putSerializable(ARG_WORKOUT, workout);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            workout = (WorkoutFromAPI) getArguments().getSerializable(ARG_WORKOUT);
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_active_workouts_detail, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        TextView nameTextView = view.findViewById(R.id.NameTextView);
        TextView equipmentTextView = view.findViewById(R.id.EquipmentTextView);
        TextView instructionsTextView = view.findViewById(R.id.InstructionsTextView);
        Button addButton = view.findViewById(R.id.addButton);
        fieldsContainer = view.findViewById(R.id.fieldsContainer);

        if (workout != null) {
            nameTextView.setText(workout.getName());
            equipmentTextView.setText(workout.getEquipment());
            instructionsTextView.setText(workout.getInstructions());
        } else {
            // Log or toast an error message if workout is null
            Toast.makeText(getContext(), "Workout data is missing", Toast.LENGTH_SHORT).show();
        }

        addButton.setOnClickListener(v -> addSetFields());
    }

    private void addSetFields() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View setFields = inflater.inflate(R.layout.item_set_fields_for_active_workouts, fieldsContainer, false);
        TextView setLabel = setFields.findViewById(R.id.setLabel);
        setCount++;
        setLabel.setText("Set " + setCount);

        fieldsContainer.addView(setFields);
    }
}

a class for the big frament

public class ActiveWorkoutFragment extends Fragment {

    private RecyclerView recyclerView;
    private ActiveWorkoutAdapter adapter;
    private List<WorkoutFromAPI> activeWorkouts = new ArrayList<>();

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        setRetainInstance(true); 
        return inflater.inflate(R.layout.fragment_active_workouts, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        adapter = new ActiveWorkoutAdapter(activeWorkouts, new ActiveWorkoutAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(WorkoutFromAPI workout) {
                showWorkoutDetailFragment(workout);
            }
        });
        recyclerView.setAdapter(adapter);
    }

    public void addWorkout(WorkoutFromAPI workout) {
        activeWorkouts.add(workout);
        adapter.notifyItemInserted(activeWorkouts.size() - 1);
        Toast.makeText(getContext(), "Workout added to today's list", Toast.LENGTH_SHORT).show();
    }

    private void showWorkoutDetailFragment(WorkoutFromAPI workout) {
        ActiveWorkoutsDetailFragment fragment = ActiveWorkoutsDetailFragment.newInstance(workout);
        FragmentTransaction transaction = requireActivity().getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_container, fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }
}

and a class for the detailed fragment:

public class ActiveWorkoutsDetailFragment extends Fragment {

    private static final String ARG_WORKOUT = "workout";
    private WorkoutFromAPI workout;
    private LinearLayout fieldsContainer;
    private int setCount = 0;

    public static ActiveWorkoutsDetailFragment newInstance(WorkoutFromAPI workout) {
        ActiveWorkoutsDetailFragment fragment = new ActiveWorkoutsDetailFragment();
        Bundle args = new Bundle();
        args.putSerializable(ARG_WORKOUT, workout);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            workout = (WorkoutFromAPI) getArguments().getSerializable(ARG_WORKOUT);
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_active_workouts_detail, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        TextView nameTextView = view.findViewById(R.id.NameTextView);
        TextView equipmentTextView = view.findViewById(R.id.EquipmentTextView);
        TextView instructionsTextView = view.findViewById(R.id.InstructionsTextView);
        Button addButton = view.findViewById(R.id.addButton);
        fieldsContainer = view.findViewById(R.id.fieldsContainer);

        if (workout != null) {
            nameTextView.setText(workout.getName());
            equipmentTextView.setText(workout.getEquipment());
            instructionsTextView.setText(workout.getInstructions());
        } else {
            // toast an error message if workout is null
            Toast.makeText(getContext(), "Workout data is missing", Toast.LENGTH_SHORT).show();
        }

        addButton.setOnClickListener(v -> addSetFields());
    }

    private void addSetFields() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View setFields = inflater.inflate(R.layout.item_set_fields_for_active_workouts, fieldsContainer, false);
        TextView setLabel = setFields.findViewById(R.id.setLabel);
        setCount++;
        setLabel.setText("Set " + setCount);

        fieldsContainer.addView(setFields);
    }
}

The elements that I add to the ActiveWorkoutFragment are coming from an API request.

Now, I tried to change it to mvvm and observer. I created ViewModel and Model classes, and then changed the adapter to match the changes. But when I click on the element on the list, it enters the detailed fragment(ActiveWorkoutsDetailFragment), but nothing is shown. I can navigate out, which means that somehow it open the fragment, but nothing is shown? Code for the mvvm and observer pattern:

public class ActiveWorkoutsDetailViewModel extends ViewModel {
    private final MutableLiveData<ActiveWorkoutModel> workout = new MutableLiveData<>();
    private final MutableLiveData<Integer> setCount = new MutableLiveData<>(0);

    public LiveData<ActiveWorkoutModel> getWorkout() {
        return workout;
    }

    public void setWorkout(ActiveWorkoutModel workout) {
        this.workout.setValue(workout);
    }

    public LiveData<Integer> getSetCount() {
        return setCount;
    }

    public void incrementSetCount() {
        setCount.setValue(setCount.getValue() + 1);
    }

    public void addSet(Set set) {
        ActiveWorkoutModel currentWorkout = workout.getValue();
        if (currentWorkout != null) {
            currentWorkout.getSets().add(set);
            workout.setValue(currentWorkout);
        }
    }
}
public class ActiveWorkoutsDetailFragment extends Fragment {

    private static final String ARG_WORKOUT = "workout";
    private ActiveWorkoutsDetailViewModel viewModel;
    private LinearLayout fieldsContainer;
    private Button addButton;
    private TextView nameTextView;
    private TextView equipmentTextView;
    private TextView instructionsTextView;

    public static ActiveWorkoutsDetailFragment newInstance(ActiveWorkoutModel workout) {
        ActiveWorkoutsDetailFragment fragment = new ActiveWorkoutsDetailFragment();
        Bundle args = new Bundle();
        args.putSerializable(ARG_WORKOUT, workout);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(ActiveWorkoutsDetailViewModel.class);
        if (getArguments() != null) {
            ActiveWorkoutModel workout = (ActiveWorkoutModel) getArguments().getSerializable(ARG_WORKOUT);
            viewModel.setWorkout(workout);
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_active_workouts_detail, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        nameTextView = view.findViewById(R.id.NameTextView);
        equipmentTextView = view.findViewById(R.id.EquipmentTextView);
        instructionsTextView = view.findViewById(R.id.InstructionsTextView);
        addButton = view.findViewById(R.id.addButton);
        fieldsContainer = view.findViewById(R.id.fieldsContainer);

        viewModel.getWorkout().observe(getViewLifecycleOwner(), workout -> {
            if (workout != null) {
                nameTextView.setText(workout.getName());
                equipmentTextView.setText(workout.getEquipment());
                instructionsTextView.setText(workout.getInstructions());
            }
        });

        viewModel.getSetCount().observe(getViewLifecycleOwner(), count -> {
            fieldsContainer.removeAllViews();
            for (int i = 0; i < count; i++) {
                addSetFields(i + 1);
            }
        });

        addButton.setOnClickListener(v -> {
            viewModel.incrementSetCount();
            Set newSet = new Set(viewModel.getSetCount().getValue(), 0, 0); // Replace with actual data if available
            viewModel.addSet(newSet);
        });
    }

    private void addSetFields(int setNumber) {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View setFields = inflater.inflate(R.layout.item_set_fields_for_active_workouts, fieldsContainer, false);
        TextView setLabel = setFields.findViewById(R.id.setLabel);
        setLabel.setText("Set " + setNumber);
        fieldsContainer.addView(setFields);
    }
}
public class ActiveWorkoutFragment extends Fragment {

    private RecyclerView recyclerView;
    private ActiveWorkoutAdapter adapter;
    private List<ActiveWorkoutModel> activeWorkouts = new ArrayList<>();

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        setRetainInstance(true); // Retain this fragment instance across configuration changes
        return inflater.inflate(R.layout.fragment_active_workouts, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        adapter = new ActiveWorkoutAdapter(activeWorkouts, new ActiveWorkoutAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(ActiveWorkoutModel workout) {
                showWorkoutDetailFragment(workout);
            }
        });
        recyclerView.setAdapter(adapter);
    }

    public void addWorkout(ActiveWorkoutModel workout) {
        activeWorkouts.add(workout);
        adapter.notifyItemInserted(activeWorkouts.size() - 1);
        Toast.makeText(getContext(), "Workout added to today's list", Toast.LENGTH_SHORT).show();
    }

    private void showWorkoutDetailFragment(ActiveWorkoutModel workout) {
        ActiveWorkoutsDetailFragment fragment = ActiveWorkoutsDetailFragment.newInstance(workout);
        FragmentTransaction transaction = requireActivity().getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_container, fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    private ActiveWorkoutModel convertToActiveWorkoutModel(WorkoutFromAPI workoutFromAPI) {
        return new ActiveWorkoutModel(
                workoutFromAPI.getName(),
                workoutFromAPI.getEquipment(),
                workoutFromAPI.getInstructions(),
                new ArrayList<>()
        );
    }
}
public class ActiveWorkoutAdapter extends RecyclerView.Adapter<ActiveWorkoutAdapter.HomeWorkoutViewHolder> {

    private List<ActiveWorkoutModel> workoutList;
    private OnItemClickListener listener;

    public interface OnItemClickListener {
        void onItemClick(ActiveWorkoutModel workout);
    }

    public ActiveWorkoutAdapter(List<ActiveWorkoutModel> workoutList, OnItemClickListener listener) {
        this.workoutList = workoutList;
        this.listener = listener;
    }

    @NonNull
    @Override
    public HomeWorkoutViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_active_workout, parent, false);
        return new HomeWorkoutViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull HomeWorkoutViewHolder holder, int position) {
        ActiveWorkoutModel workout = workoutList.get(position);
        holder.bind(workout, listener);
    }

    @Override
    public int getItemCount() {
        return workoutList != null ? workoutList.size() : 0;
    }

    public void updateWorkoutList(List<ActiveWorkoutModel> newList) {
        workoutList = newList;
        notifyDataSetChanged();
    }

    public static class HomeWorkoutViewHolder extends RecyclerView.ViewHolder {
        private final TextView workoutName;

        public HomeWorkoutViewHolder(View itemView) {
            super(itemView);
            workoutName = itemView.findViewById(R.id.homeWorkoutNameTextView);
        }

        public void bind(ActiveWorkoutModel workout, OnItemClickListener listener) {
            workoutName.setText(workout.getName());
            itemView.setOnClickListener(v -> {
                if (listener != null) {
                    listener.onItemClick(workout);
                }
            });
        }
    }
}

Any ideas, tips are more than welcome.

Upvotes: 0

Views: 27

Answers (0)

Related Questions