📱

[趣味開発]タスク管理通知アプリの開発 タスク管理編

2024/10/12に公開

[趣味開発]タスク管理通知アプリの開発 タスク管理編

はじめに

https://zenn.dev/yydevelop/articles/82a42a3f3b8c6e
の続き

はじめに

今回は、チェックボックス付きのタスクリストを表示し、完了したタスクを削除できるAndroidウィジェットの実装します。

複数タスクの登録

build.gradle.kts
dependenciesに以下の行を追加

implementation("com.google.code.gson:gson:2.8.8")

MainActivity.kt の修正

package com.example.taskreminder

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.taskreminder.ui.theme.TaskReminderTheme
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            TaskReminderTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    TaskInputScreen(
                        modifier = Modifier.padding(innerPadding),
                        sharedPreferences = getSharedPreferences("TaskReminderPrefs", Context.MODE_PRIVATE)
                    )
                }
            }
        }
    }
}

@Composable
fun TaskInputScreen(modifier: Modifier = Modifier, sharedPreferences: SharedPreferences) {
    var task by remember { mutableStateOf("") }
    val context = LocalContext.current

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        TextField(
            value = task,
            onValueChange = { task = it },
            label = { Text("タスクを入力") },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = {
                // 既存のタスクリストを取得
                val taskList = getTaskList(sharedPreferences)
                taskList.add(task)

                // 更新されたタスクリストを保存
                saveTaskList(sharedPreferences, taskList)

                // ウィジェットを更新
                val appWidgetManager = AppWidgetManager.getInstance(context)
                val widgetComponent = ComponentName(context, TaskReminderWidget::class.java)
                val appWidgetIds = appWidgetManager.getAppWidgetIds(widgetComponent)
                for (appWidgetId in appWidgetIds) {
                    TaskReminderWidget.updateAppWidget(context, appWidgetManager, appWidgetId) // メソッドを呼び出す
                }

                task = ""
            },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("タスクを追加")
        }
    }
}

// タスクリストを取得
fun getTaskList(sharedPreferences: SharedPreferences): MutableList<String> {
    val gson = Gson()
    val json = sharedPreferences.getString("taskList", null)
    val type = object : TypeToken<MutableList<String>>() {}.type
    return if (json != null) {
        gson.fromJson(json, type)
    } else {
        mutableListOf()
    }
}

// タスクリストを保存
fun saveTaskList(sharedPreferences: SharedPreferences, taskList: MutableList<String>) {
    val gson = Gson()
    val json = gson.toJson(taskList)
    val editor = sharedPreferences.edit()
    editor.putString("taskList", json)
    editor.apply()
}

@Preview(showBackground = true)
@Composable
fun TaskInputScreenPreview() {
    val context = LocalContext.current
    val sharedPreferences = context.getSharedPreferences("TaskReminderPrefs", Context.MODE_PRIVATE)
    TaskReminderTheme {
        TaskInputScreen(sharedPreferences = sharedPreferences)
    }
}

TaskReminderWidget.kt の修正
ウィジェットに複数のタスクをリストとして表示し、完了ボタンを実装します。

package com.example.taskreminder

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.widget.RemoteViews
import android.widget.Toast
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

class TaskReminderWidget : AppWidgetProvider() {

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)

        if (intent.action == "TASK_COMPLETED") {
            val taskIndex = intent.getIntExtra("taskIndex", -1)
            if (taskIndex != -1) {
                val sharedPreferences = context.getSharedPreferences("TaskReminderPrefs", Context.MODE_PRIVATE)
                val taskList = getTaskList(sharedPreferences)
                taskList.removeAt(taskIndex) // タスクをリストから削除
                saveTaskList(sharedPreferences, taskList)

                val appWidgetManager = AppWidgetManager.getInstance(context)
                val thisAppWidget = ComponentName(context.packageName, javaClass.name)
                val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
                for (appWidgetId in appWidgetIds) {
                    updateAppWidget(context, appWidgetManager, appWidgetId)
                }

                Toast.makeText(context, "タスクが完了しました", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {}
    override fun onDisabled(context: Context) {}

    companion object {
        internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
            val views = RemoteViews(context.packageName, R.layout.task_reminder_widget)

            // SharedPreferencesからタスクリストを取得
            val sharedPreferences = context.getSharedPreferences("TaskReminderPrefs", Context.MODE_PRIVATE)
            val taskList = getTaskList(sharedPreferences)

            // タスクリストを表示
            val taskDisplay = taskList.joinToString(separator = "\n")
            views.setTextViewText(R.id.widgetTaskText, taskDisplay)

            // 完了ボタンのPendingIntentを設定(各タスクごと)
            taskList.forEachIndexed { index, _ ->
                val intent = Intent(context, TaskReminderWidget::class.java)
                intent.action = "TASK_COMPLETED"
                intent.putExtra("taskIndex", index)
                val pendingIntent = PendingIntent.getBroadcast(context, index, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
                views.setOnClickPendingIntent(R.id.completeTaskButton, pendingIntent)
            }

            appWidgetManager.updateAppWidget(appWidgetId, views)
        }

        // タスクリストを取得
        fun getTaskList(sharedPreferences: SharedPreferences): MutableList<String> {
            val gson = Gson()
            val json = sharedPreferences.getString("taskList", null)
            val type = object : TypeToken<MutableList<String>>() {}.type
            return if (json != null) {
                gson.fromJson(json, type)
            } else {
                mutableListOf()
            }
        }

        // タスクリストを保存
        fun saveTaskList(sharedPreferences: SharedPreferences, taskList: MutableList<String>) {
            val gson = Gson()
            val json = gson.toJson(taskList)
            val editor = sharedPreferences.edit()
            editor.putString("taskList", json)
            editor.apply()
        }
    }
}

修正後

完了動作のチェックリスト化

1. task_reminder_widget.xml の修正

既存の task_reminder_widget.xml では、1つのタスクしか表示できませんでしたが、複数のタスクを表示するために、タスクコンテナを追加して複数のタスクをリスト表示できるように修正します。

task_reminder_widget.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <!-- タスクとチェックボックスを表示するコンテナ -->
    <LinearLayout
        android:id="@+id/taskContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

</LinearLayout>

2. TaskReminderWidget.kt の修正

package com.example.taskreminder

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.widget.RemoteViews
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

class TaskReminderWidget : AppWidgetProvider() {

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)

        if (intent.action == "TASK_COMPLETED") {
            val taskIndex = intent.getIntExtra("taskIndex", -1)
            if (taskIndex != -1) {
                val sharedPreferences = context.getSharedPreferences("TaskReminderPrefs", Context.MODE_PRIVATE)
                val taskList = getTaskList(sharedPreferences)
                taskList.removeAt(taskIndex) // タスクをリストから削除
                saveTaskList(sharedPreferences, taskList)

                val appWidgetManager = AppWidgetManager.getInstance(context)
                val thisAppWidget = ComponentName(context.packageName, javaClass.name)
                val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
                for (appWidgetId in appWidgetIds) {
                    updateAppWidget(context, appWidgetManager, appWidgetId)
                }
            }
        }
    }

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {}
    override fun onDisabled(context: Context) {}

    companion object {
        internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
            val views = RemoteViews(context.packageName, R.layout.task_reminder_widget)

            // SharedPreferencesからタスクリストを取得
            val sharedPreferences = context.getSharedPreferences("TaskReminderPrefs", Context.MODE_PRIVATE)
            val taskList = getTaskList(sharedPreferences)

            // タスクコンテナをクリアして、チェックボックスとタスクを追加
            views.removeAllViews(R.id.taskContainer)

            taskList.forEachIndexed { index, task ->
                // 各タスクごとにレイアウトを設定
                val taskView = RemoteViews(context.packageName, R.layout.task_item) // task_item.xml を使用
                taskView.setTextViewText(R.id.taskNameText, task)

                // チェックボックスのPendingIntentを設定
                val intent = Intent(context, TaskReminderWidget::class.java)
                intent.action = "TASK_COMPLETED"
                intent.putExtra("taskIndex", index)
                val pendingIntent = PendingIntent.getBroadcast(context, index, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
                taskView.setOnClickPendingIntent(R.id.taskCheckBox, pendingIntent)

                // タスクコンテナに追加
                views.addView(R.id.taskContainer, taskView)
            }

            appWidgetManager.updateAppWidget(appWidgetId, views)
        }

        // タスクリストを取得
        fun getTaskList(sharedPreferences: SharedPreferences): MutableList<String> {
            val gson = Gson()
            val json = sharedPreferences.getString("taskList", null)
            val type = object : TypeToken<MutableList<String>>() {}.type
            return if (json != null) {
                gson.fromJson(json, type)
            } else {
                mutableListOf()
            }
        }

        // タスクリストを保存
        fun saveTaskList(sharedPreferences: SharedPreferences, taskList: MutableList<String>) {
            val gson = Gson()
            val json = gson.toJson(taskList)
            val editor = sharedPreferences.edit()
            editor.putString("taskList", json)
            editor.apply()
        }
    }
}

3. task_item.xml の新規作成

各タスクを表示するためのレイアウトファイル task_item.xml を新規作成します。このファイルでは、タスク名とチェックボックスを横並びで表示します。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="8dp">

    <!-- 左側にチェックボックスを配置 -->
    <CheckBox
        android:id="@+id/taskCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical" />

    <!-- タスク名を右側に配置 -->
    <TextView
        android:id="@+id/taskNameText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="タスク名"
        android:textSize="16sp"
        android:layout_marginStart="8dp" />
</LinearLayout>

画面表示

Androidウィジェットで複数のタスクを管理し、チェックボックスでタスクを完了できるようになりました。

※ChatGPTとの壁打ちの様子

Discussion