vitvlkv
vitvlkv

Reputation: 852

Custom Dialog with RecyclerView doesn't show OK Cancel buttons

I have a simple custom multi select items dialog. If number of items is small, then the dialog works fine and shows OK and Cancel buttons at the bottom. But if there are many items (so you have to scroll the list) - no buttons are shown. I've searched the SO for my problem with no luck. I've tested my dialog on Android API 27..29 - it's the same. Maybe I'm missing something important in the layout properties etc...

Here is my code and xml:

package ru.vitvlkv.myapp.gui;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;
import java.util.Set;

import ru.vitvlkv.myapp.R;
import ru.vitvlkv.myapp.playlists.Exercise;

public class SelectExercisesDialog extends DialogFragment {
    private static final String TAG = SelectExercisesDialog.class.getSimpleName();

    private final String message;
    private final List<Exercise> exercises;
    private RecyclerView recyclerView;
    private final SelectExercisesDialog.OnOKClickListener onOKButtonClick;
    private final Set<String> selectedExercisesIds;

    public interface OnOKClickListener {
        void onClick(Set<String> exerciseIds);
    }

    public SelectExercisesDialog(String message, List<Exercise> exercises, Set<String> selectedExercisesIds,
                                 final SelectExercisesDialog.OnOKClickListener onButtonOKClick) {
        this.message = message;
        this.exercises = exercises;
        this.selectedExercisesIds = selectedExercisesIds;
        this.onOKButtonClick = onButtonOKClick;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = requireActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.select_exercises_dialog, null);
        recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        recyclerView.setAdapter(new AllExercisesAdapter(exercises, selectedExercisesIds));

        setRetainInstance(true);

        return new AlertDialog.Builder(getActivity())
            .setView(view)
            .setMessage(message)
            .setPositiveButton(R.string.dialog_button_ok, (DialogInterface dialog, int id) -> {
                onOKButtonClick.onClick(selectedExercisesIds);
            })
            .setNegativeButton(R.string.dialog_button_cancel, (DialogInterface dialog, int id) -> {
            })
            .create();
    }

    private static class AllExercisesAdapter extends RecyclerView.Adapter<ExerciseViewHolder> {
        private final List<Exercise> exercises;
        private final Set<String> checkedExercisesIds;

        public AllExercisesAdapter(List<Exercise> exercises, Set<String> checkedExercisesIds) {
            this.exercises = exercises;
            this.checkedExercisesIds = checkedExercisesIds;
        }

        @NonNull
        @Override
        public ExerciseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.select_exercise_item_view, parent, false);

            return new ExerciseViewHolder(view, checkedExercisesIds);
        }

        @Override
        public void onBindViewHolder(@NonNull ExerciseViewHolder holder, int position) {
            Exercise exercise = exercises.get(position);
            holder.setExercise(exercise);
        }

        @Override
        public int getItemCount() {
            return exercises.size();
        }
    }


    private static class ExerciseViewHolder extends RecyclerView.ViewHolder {
        private Exercise exercise = null;
        private final TextView textView;
        public final CheckBox checkBox;
        private final Set<String> checkedExercisesIds;

        public ExerciseViewHolder(@NonNull View view, Set<String> checkedExercisesIds) {
            super(view);
            this.checkedExercisesIds = checkedExercisesIds;
            textView = view.findViewById(R.id.textView);
            checkBox = view.findViewById(R.id.checkBox);
            checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
                if (isChecked) {
                    checkedExercisesIds.add(exercise.getId());
                } else {
                    checkedExercisesIds.remove(exercise.getId());
                }
            });
        }

        public void setExercise(Exercise exercise) {
            this.exercise = exercise;
            textView.setText(exercise.getName());
            checkBox.setChecked(checkedExercisesIds.contains(exercise.getId()));
        }
    }
}

layout/select_exercises_dialog.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

layout/select_exercise_item_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="@dimen/list_item_height"
    android:layout_marginLeft="@dimen/margin_medium"
    android:layout_marginRight="@dimen/margin_medium"
    android:gravity="center_vertical">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/margin_medium"
            android:text="MyExercise"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <CheckBox
            android:id="@+id/checkBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>

And the Exercise class is just a POJO:

package ru.vitvlkv.myapp.playlists;

public class Exercise {
    private final String id;

    private final String name;

    private Exercise(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String getId() {
        return id;
    }
}

How can I fix this and make the dialog to show OK Cancel buttons in all the cases? Any help is appreciated.

Thank you!

P.S. The screenshot of the dialog:

Dialog without buttons

Upvotes: 1

Views: 544

Answers (3)

Ahmad Najar
Ahmad Najar

Reputation: 66

If you are Using dialog fragment It's fragment in style of dialog with the some of the dialog specs now if you want to add the ok and cancel you have two chooses

  1. extend the class from AlertDialog.Builder and on create add the positive and the negative button handler and the text

example about the code will be look like this

class ExampleDialog(context: Context) : AlertDialog.Builder(context) {


private val view by lazy {
    LayoutInflater.from(context).inflate(R.layout.dialog_exmaple, null, false)
}

override fun setView(layoutResId: Int): AlertDialog.Builder {

    // do the recylceview init from the view code here 

    return super.setView(view)
}


override fun setPositiveButton(
    text: CharSequence?,
    listener: DialogInterface.OnClickListener?
): AlertDialog.Builder {
    return super.setPositiveButton(
        context.getText(R.string.action_ok)
    ) { p0, p1 ->


    }
}


override fun setNegativeButton(
    text: CharSequence?,
    listener: DialogInterface.OnClickListener?
): AlertDialog.Builder {
    return super.setNegativeButton(
        context.getText(R.string.action_cancel)
    ) { p0, p1 ->


    }
}

}

  • note I don't like this way allot

    1. you can just add inside you'r xml two button's to the bottom of the recycle-view and add the text to them like ok and cancel and handle the onClick by high order function or by interface it's up to you I can show you and example below

I prefer this code more clean for me and add click listener in the dialogFragment class

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">


<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/listData"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@id/actionOk"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />


<com.google.android.material.button.MaterialButton
    android:id="@+id/actionOk"
    style="@style/Widget.MaterialComponents.Button.OutlinedButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    android:text="@string/action_ok"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toBottomOf="@id/listData" />

<com.google.android.material.button.MaterialButton
    android:id="@+id/actionCancel"
    style="@style/Widget.MaterialComponents.Button.OutlinedButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    android:text="@string/action_cancel"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@id/actionOk"
    app:layout_constraintTop_toBottomOf="@id/listData" />


  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState)
    val root = ConstraintLayout(activity)
    root.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
    dialog.setContentView(root)
    dialog.window?.let {
        it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        it.setWindowAnimations(R.style.DialogAnimationNormal)
    }
    dialog.setCanceledOnTouchOutside(true)
    return dialog
}

Upvotes: 1

Ashita Asati
Ashita Asati

Reputation: 1

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent"
android:minHeight= "200dp">


        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/margin_medium"
        android:layout_marginRight="@dimen/margin_medium"
        android:gravity="center_vertical">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/margin_medium"
                android:text="MyExercise"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <CheckBox
                android:id="@+id/checkBox"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </FrameLayout>

Upvotes: 0

vitvlkv
vitvlkv

Reputation: 852

I improvised around the solution suggested by @radesh and came to this (still it's not exactly what I need):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/linearLayout3"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:minHeight="150dp">

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

This layout at least displays the OK Cancel buttons and the list with items. But still in this case my dialog doesn't want to occupy all the available space of the device screen. The list recyclerView is very limited (it's only about 150dp in height). Of course I want the dialog to occupy as much space as available.

enter image description here

Upvotes: 0

Related Questions