[趣味開発]タスク管理通知アプリの開発 ウィジェット編

![](https://storage.googleapis.com/zenn-user-upload/a8b1145ec5c7-20241006.png)
# [趣味開発]タスク管理通知アプリの開発 ウィジェット編

## はじめに
今回は、物忘れが激しい自分向けに、簡単なウィジェットを使ったタスク管理アプリ「TaskReminder」を開発しています。備忘もかねてブログに残しておくこととします。
この記事では、まずシンプルなウィジェットを表示するところまでの手順を中心に説明します。

## 1. プロジェクトの初期設定
### プロジェクト名: 「TaskReminder」
- Android Studioを開き、新しいプロジェクトを「TaskReminder」という名前で作成します。
- New → Widget → App Widget で TaskReminderWidget を自動生成します。
- ビルドし、ウィジェットが正常に表示されることを確認します。

### 手順
- プロジェクト作成時に Empty Activity を選択し、Kotlinを使用します。
- app → New → Widget → App Widget を選び、名前を TaskReminderWidget としてウィジェットを生成します。

## 2.タスク表示機能の追加

次に、ウィジェットに「今日のタスク」を表示させる機能を追加します。
`TaskReminderWidget.kt`の修正
```kotlin
package com.example.taskreminder

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

class TaskReminderWidget : AppWidgetProvider() {
    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) {}
}

internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
    val views = RemoteViews(context.packageName, R.layout.task_reminder_widget)
    views.setTextViewText(R.id.widgetTaskText, "今日のタスク: ミーティング")

    appWidgetManager.updateAppWidget(appWidgetId, views)
}
```

次に、ウィジェットレイアウトファイル `task_reminder_widget.xml` を修正します。

`res/layout/task_reminder_widget.xml` の内容

```xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:id="@+id/widgetTaskText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="タスクなし"
        android:textSize="18sp"
        android:textColor="#000000"/>

</RelativeLayout>
```

上記の修正を加えた後、プロジェクトをビルドし、エミュレータでテストします。

## 3.チェック機能の追加

次に、タスクの完了ボタンを追加して、タスクが完了したことを確認できるようにします。
`TaskReminderWidget.kt` の修正

```kotlin
package com.example.taskreminder

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews

class TaskReminderWidget : AppWidgetProvider() {
    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) {}
}

internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
    val views = RemoteViews(context.packageName, R.layout.task_reminder_widget)
    views.setTextViewText(R.id.widgetTaskText, "今日のタスク: ミーティング")

    // タスク完了ボタンを設定
    val intent = Intent(context, TaskReminderWidget::class.java)
    intent.action = "TASK_COMPLETED"
    val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    views.setOnClickPendingIntent(R.id.completeTaskButton, pendingIntent)

    appWidgetManager.updateAppWidget(appWidgetId, views)
}

```

ウィジェットレイアウトの追加修正
完了ボタンを追加します。
`res/layout/task_reminder_widget.xml` の修正

```xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:id="@+id/widgetTaskText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="タスクなし"
        android:textSize="18sp"
        android:textColor="#000000"/>

    <Button
        android:id="@+id/completeTaskButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="完了"
        android:layout_below="@id/widgetTaskText"
        android:layout_marginTop="8dp"/>

</RelativeLayout>
```

### 再度ビルド・テスト
ビルドし、ウィジェット上の「完了」ボタンが正しく機能するか確認します。

![](https://storage.googleapis.com/zenn-user-upload/c02ad2b991e7-20241006.png)

## 3.完了ボタンアクション

### 完了ボタン押下時の処理の実装
TaskReminderWidget.ktの修正
「完了」ボタンが押された際にタスクが完了したことを示す表示に変更する処理を追加します。

```kotlin
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.widget.RemoteViews
import android.widget.Toast

class TaskReminderWidget : AppWidgetProvider() {

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

        if (intent.action == "TASK_COMPLETED") {
            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) {}
}

internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, taskStatus: String = "今日のタスク: ミーティング") {
    val views = RemoteViews(context.packageName, R.layout.task_reminder_widget)
    views.setTextViewText(R.id.widgetTaskText, taskStatus)

    // 完了ボタンのPendingIntentを設定
    val intent = Intent(context, TaskReminderWidget::class.java)
    intent.action = "TASK_COMPLETED"
    val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    views.setOnClickPendingIntent(R.id.completeTaskButton, pendingIntent)

    appWidgetManager.updateAppWidget(appWidgetId, views)
}
```

AndroidManifest.xml
```
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TaskReminder"
        tools:targetApi="31">
        <receiver
            android:name=".TaskReminderWidget"
            android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="TASK_COMPLETED"/>
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/task_reminder_widget_info" />
        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.TaskReminder">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

### ビルド・確認
完了ボタンのアクションを追加
![](https://storage.googleapis.com/zenn-user-upload/d232d7939fce-20241006.png)
![](https://storage.googleapis.com/zenn-user-upload/a94da7383df8-20241006.png)