jeudi 3 février 2022

Saving checkbox states in RecyclerView inside Fragment using SharedPreferences or any other method

I'm trying to save checkbox states in RecyclerView inside Fragment to restore these preferences after exit from the app and loading it again.

I have a ConfigActivity for AppWidget in which there are fragments. Inside of one of the fragments I have a RecyclerView which loads calendars available for the user from Calendar Provider. Based on selected calendars the appwidget will be loading the events from them. Selected calendars should be passed into the appwidget.

I've made saving states of the checkboxes while scrolling of the RecyclerView.

But I don't know how to save selected checkboxes in RecyclerView inside Fragment using SharedPreferences (saving for reopening of the app).

My data class for calendar items:

data class CalendarItem(
    val idCalendar: Long,
    val displayNameCalendar: String?,
    val accountNameCalendar: String?,
    val colorCalendar: Int? 
)

Item with checkbox:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">

<ImageView
    android:id="@+id/calendar_color"
    android:layout_width="10dp"
    android:layout_height="10dp"
    android:src="@drawable/color_label_circle"
    app:tint="@color/accent_color"
    android:layout_alignParentStart="true"
    android:layout_alignTop="@+id/text_display_name_calendar"
    android:layout_alignBottom="@+id/text_display_name_calendar"/>

<com.google.android.material.checkbox.MaterialCheckBox
    android:id="@+id/text_display_name_calendar"
    style="@style/basicText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentStart="true"
    android:layout_alignParentTop="true"
    android:layout_marginStart="24dp"
    android:layout_marginEnd="4dp"
    android:maxLines="1"
    android:ellipsize="end"
    android:gravity="start|center_vertical"
    android:layoutDirection="rtl"
    android:text="Display Name" />

<TextView
    android:id="@+id/text_account_name"
    style="@style/commentText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Account Name"
    android:layout_marginEnd="4dp"
    android:maxLines="1"
    android:ellipsize="end"
    android:layout_alignStart="@+id/text_display_name_calendar"
    android:layout_below="@+id/text_display_name_calendar" /> 
</RelativeLayout>

My Fragment getting calendars:

class CalendarsEventsFragment : Fragment() {

// For permissions
private val PERMISSION_REQUEST_CODE = 101

// For RecyclerView - Calendars
private lateinit var calendarItemAdapter: CalendarItemAdapter
private lateinit var recyclerViewCalendars: RecyclerView

// Values for the calendars from the calendar content provider
private val EVENT_PROJECTION = arrayOf(
    CalendarContract.Calendars._ID,
    CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
    CalendarContract.Calendars.ACCOUNT_NAME,
    CalendarContract.Calendars.CALENDAR_COLOR
    )

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    // Inflate the layout for this fragment
    val view =  inflater.inflate(R.layout.fragment_calendars_events, container, false)

    recyclerViewCalendars = view.findViewById(R.id.recyclerview_calendars)

    // Setup permissions + start getCalendars
    setupPermissionsGetCalendars()

    return view
}

// Function to get and show calendars
private fun getCalendars() {
    // Getting calendars from CalendarProvider
    // In practice, should be done in an asynchronous thread instead of on the main thread
    calendarItemAdapter = CalendarItemAdapter()
    calendarItemAdapter.clearData()
    val uri = CalendarContract.Calendars.CONTENT_URI
    val cur: Cursor? = context?.contentResolver?.query(
        uri,
        EVENT_PROJECTION,
        null,
        null,
        null
    )
    while (cur?.moveToNext() == true) {
        val calId = cur.getLong(PROJECTION_ID_INDEX)
        val displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX)
        val accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX)
        val color = cur.getInt(PROJECTION_CALENDAR_COLOR_INDEX)
        calendarItemAdapter.pushData(
            CalendarItem(
                idCalendar = calId,
                displayNameCalendar = displayName,
                accountNameCalendar = accountName,
                colorCalendar = color
            )
        )
    }
    cur?.close()
    // Setup RecyclerView adapter
    recyclerViewCalendars.let {
        it.layoutManager = LinearLayoutManager(context)
        it.adapter = calendarItemAdapter
    }
}

// Function to check permission and make request for permission + start getCalendars
private fun setupPermissionsGetCalendars() {
    if (checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) !=
        PackageManager.PERMISSION_GRANTED
    ) {
        requestPermissions(
            arrayOf(Manifest.permission.READ_CALENDAR),
            PERMISSION_REQUEST_CODE
        )
    } else {
        getCalendars()
    }
}

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    when (requestCode) {
        PERMISSION_REQUEST_CODE -> {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(
                    requireActivity(),
                    getText((R.string.toast_permission_granted)),
                    Toast.LENGTH_SHORT
                ).show()
                getCalendars()
            } else {
                if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CALENDAR)) {
                    Toast.makeText(
                        requireActivity(),
                        getText((R.string.toast_permission_denied)),
                        Toast.LENGTH_SHORT
                    ).show()
                    showUserRationale()
                } else {
                    askUserOpenAppInfo()
                }
            }
        }
    }
}

private fun showUserRationale() {
    AlertDialog.Builder(requireContext())
        .setTitle(getString(R.string.request_permission_rationale_title))
        .setMessage(getString(R.string.request_permission_rationale_message))
        .setPositiveButton("OK") { dialog, id ->
            requestPermissions(
                arrayOf(Manifest.permission.READ_CALENDAR),
                PERMISSION_REQUEST_CODE
            )
        }
        .create()
        .show()
}

private fun askUserOpenAppInfo() {
    val appSettingsIntent = Intent(
        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
        Uri.fromParts("package", activity?.packageName, null)
    )
    if (activity?.packageManager?.resolveActivity(
            appSettingsIntent,
            PackageManager.MATCH_DEFAULT_ONLY
        ) == null
    ) {
        Toast.makeText(
            requireContext(),
            getText(R.string.toast_permission_denied_forever),
            Toast.LENGTH_SHORT
        ).show()
    } else {
        AlertDialog.Builder(requireContext())
            .setTitle(getString(R.string.request_permission_denied_forever_title))
            .setMessage(getString(R.string.request_permission_denied_forever_message))
            .setPositiveButton(getString(R.string.open_app_info_dialog_positive_button_text)) { dialog, id ->
                startActivity(appSettingsIntent)
                requireActivity().finish()
            }
            .setNegativeButton(getString(R.string.open_app_info_dialog_negative_button_text)) { dialog, id ->
                requireActivity().finish()
            }
            .create()
            .show()
    }
} 
}

My RecyclerView Adapter:

class CalendarItemAdapter() : RecyclerView.Adapter<CalendarItemAdapter.ViewHolder>() {

var data: MutableList<CalendarItem> = mutableListOf()
var checkedCalendarItems = SparseBooleanArray()

fun clearData() {
    data.clear()
    notifyDataSetChanged()
}

fun pushData(calendarItem: CalendarItem) {
    data.add(calendarItem)
    notifyDataSetChanged()
}

inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val imageViewColor: ImageView = view.findViewById(R.id.calendar_color)
    val displayNameOfCalendar: CheckBox = view.findViewById(R.id.text_display_name_calendar)
    val accountName: TextView = view.findViewById(R.id.text_account_name)

    init {
        displayNameOfCalendar.setOnClickListener {
            if(!checkedCalendarItems.get(adapterPosition, false)) {
                displayNameOfCalendar.isChecked = true
                checkedCalendarItems.put(adapterPosition, true)
            } else {
                displayNameOfCalendar.isChecked = false
                checkedCalendarItems.put(adapterPosition, false)
            }
        }
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context)
        .inflate(R.layout.item_calendar, parent, false)
    return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val datum = data[position]
    datum.colorCalendar?.let {
        holder.imageViewColor.setColorFilter(it)
    }
    holder.displayNameOfCalendar.text = datum.displayNameCalendar
    holder.displayNameOfCalendar.isChecked = checkedCalendarItems.get(position, false)
    holder.accountName.text = datum.accountNameCalendar
}

override fun getItemCount(): Int {
    return data.size
}
}

Could you help me, please?




Aucun commentaire:

Enregistrer un commentaire