TODO app — Delete multiple items with undo timer.

  • User can can inline delete a task.
  • When the user clicks the delete button, they should see a countdown on the item from 3 to 0, along with an inline undo option.
  • Hitting the undo option should cancel the timer and leave the item as is.
  • If the timer reaches 0, the item will then be deleted.
var timer: CountDownTimer? = null        
var isTicking: Boolean = false
class ViewHolder private constructor(val binding: TaskItemBinding) :
RecyclerView.ViewHolder(binding.root) {
var timer: CountDownTimer? = null
var isTicking: Boolean = false
fun bind(viewModel: TasksViewModel, item: Task) { binding.viewmodel = viewModel
binding.task = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = TaskItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools">

<data>

<import type="android.widget.CompoundButton" />

<variable
name="task"
type="com.example.android.architecture.blueprints.todoapp.data.Task" />

<variable
name="viewmodel"
type="com.example.android.architecture.blueprints.todoapp.tasks.TasksViewModel" />
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:onClick="@{() -> viewmodel.openTask(task.id)}"
android:orientation="horizontal"
android:paddingBottom="@dimen/list_item_padding"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/list_item_padding">

<CheckBox
android:id="@+id/complete_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="@{task.completed}"
android:onClick="@{(view) -> viewmodel.completeTask(task, ((CompoundButton)view).isChecked())}" />

<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_weight="1"
android:text="@{task.titleForList}"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
app:completedTask="@{task.completed}" />

<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
>

<ImageView
android:id="@+id/deleteBt"
android:layout_width="30dp"
android:layout_height="30dp"
android:tint="@android:color/holo_red_light"
android:src="@drawable/ic_delete_black_24dp"
/>

<ProgressBar
android:id="@+id/progressBar"
android:layout_width="30dp"
android:layout_height="30dp"
android:indeterminate="false"
android:progressDrawable="@drawable/circular_progress_bar"
android:background="@drawable/circle_shape"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:visibility="gone"
android:progress="0" />

</FrameLayout>
</LinearLayout>

<Button
android:id="@+id/undoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="end"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:text="Undo"
android:layout_marginRight="@dimen/activity_horizontal_margin" />
</LinearLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.5"
android:shape="ring"
android:thickness="3dp"
android:useLevel="true">

<gradient
android:angle="0"
android:endColor="#007DD6"
android:startColor="#007DD6"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadiusRatio="2.5"
android:thickness="3.5dp"
android:useLevel="false">

<solid android:color="#CCC" />

</shape>
companion object {
const val UNDO_TIMER: Long = 3000
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)

holder.apply {

if (timer != null) {
timer?.cancel()
}

timer = object : CountDownTimer(TasksFragment.UNDO_TIMER, 100) {
override fun onTick(millisUntilFinished: Long) {
val per = (millisUntilFinished.toFloat().div(TasksFragment.UNDO_TIMER.toFloat())).times(100)
binding.progressBar.progress = (100 - per).toInt()
}

override fun onFinish() {
if (!isTicking) return //its not ticking (may be undo has been pressed)
processDelete(item, position)

}
}

binding.deleteBt.setOnClickListener {
isTicking = true // start the timer
timer?.start()
viewStateDeleting()
}

binding.undoButton.setOnClickListener {
if (isTicking) {
isTicking = false
timer?.cancel()
}
viewStateReadyToDelete()
}

bind(viewModel, item)
}
}
private fun ViewHolder.viewStateDeleting() {
binding.deleteBt.gone()
binding.progressBar.visible()
binding.undoButton.visible()
}
private fun ViewHolder.viewStateReadyToDelete() {
binding.undoButton.gone()
binding.deleteBt.visible()
binding.progressBar.gone()
}
private fun ViewHolder.processDelete(item: Task, position: Int) {
viewModel.deleteTask(item)
viewStateReadyToDelete()
isTicking = false
}
private fun View.visible() {
visibility = View.VISIBLE
}

private fun View.gone() {
visibility = View.GONE
}
fun deleteTask(task: Task) = viewModelScope.launch {
tasksRepository.deleteTask(task.id)
}
Demo of the application.

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Shortcuts that should know Android Developer

Surah Yasin

MAD Skills series: Hilt under the hood

Understanding Flutter box constraints & the unbounded heights, width error

Pentesting Android Applications-Part 1

Android OTA update: How to implement A/B update mechanism — GizmoMind

Android One Piece, Sanji Overview

💥 The Story of My First A-ha Moment With Jetpack Compose

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Akshay Kale

Akshay Kale

More from Medium

Integrating Auth Service into a Xamarin Android App

Complete Guide — In App Update for Android Apps — Google Playstore

Introduction to HMS Kits in Huawei Mobile Ecosystem

HarmonyOS: Basic Features of Lite Wearable APIs