リストウィジェットと1日タイムテーブル画像ウィジェット

widget – GASから予定を取り込み、「リスト」と「1日タイムテーブル画像」をホーム画面に出すアプリ

Google Apps Script(GAS)で管理しているスプレッドシートの予定データを、 Android アプリ側に定期的に取り込み、 ① テキストのリスト型ウィジェット と ② 画像として描画する1日タイムテーブル画像ウィジェット の二つの形で ホーム画面に表示するためのアプリです。

作品の説明

このアプリは、予定のソースをスプレッドシートに一本化しつつ、 スマホ側では「見る専用」に集中できる環境を作ることを目的にしています。 GAS がスプレッドシートから予定を JSON に変換し、 Android アプリがその JSON を取得してローカルに保存。 そこから 2 種類のホーム画面ウィジェットに描画します。

リスト型ウィジェットは、今日と明日の予定を縦に並べて確認するためのもの。 タイムテーブル画像ウィジェットは、1日を0〜24時の縦軸にした画像を Bitmap/Canvas で描画し、視覚的に「空き時間」と「詰まっている時間」を 一発で把握できるようにする試みです。

  • ① GAS(Webアプリ)でスプレッドシートから予定をJSONレスポンス化
  • ② AndroidアプリがHTTP経由で予定JSONを取得・ローカル保存
  • ③ Room or SharedPreferencesからウィジェット用データモデルを生成
  • ④ 「リスト型ウィジェット」に RemoteViews で予定一覧を表示
  • ⑤ 「1日タイムテーブル画像ウィジェット」で Bitmap/Canvas に1日分を描画
  • ⑥ 深夜0時・手動更新・同期ボタンで2種類のウィジェットを再描画

システム概要

GASと2種類のウィジェットのアーキテクチャ構成図

スプレッドシートを GAS で Web API 化し、 Android アプリがその API から JSON を取得します。 取得した予定はローカルDBに保存し、 AppWidgetProvider がリスト型ウィジェットと 1日タイムテーブル画像ウィジェットの両方に同じデータを配布する構成です。

技術スタック

  • Server-side: Google Apps Script, Google Sheets, Web Apps(JSON)
  • Android / App: Kotlin, Retrofit/OkHttp, Room
  • Android / Widgets: AppWidgetProvider, RemoteViews, Bitmap/Canvas, PendingIntent

主要コンポーネント

  • GAS Web API: スプレッドシートの行を [{ date, start, end, title, type }, ...] の JSON にして返す。
  • SyncWorker: 一定間隔 or 手動で GAS API から予定を取得し、ローカルに保存する。
  • ListWidgetProvider: 「リスト型ウィジェット」を提供する AppWidgetProvider。
  • TimetableImageWidgetProvider: Bitmap/Canvas を使って 1 日の縦タイムテーブル画像を描画するウィジェット。
  • ScheduleRepository: 今日・明日・今週などのクエリをまとめた予定データ取得レイヤ。

技術解説

GAS API

GASでスプレッドシートをJSON API化

予定の元データは Google スプレッドシートにまとめ、 GAS 側で doGet() を実装して JSON を返す Webアプリにしました。 これにより、ブラウザでもアプリでも同一の予定データを参照できます。

GAS 側のレスポンスイメージ:
function doGet(e) {
  const rows = sheet.getDataRange().getValues();
  const items = rows.slice(1).map(r => ({
    date: r[0], start: r[1], end: r[2], title: r[3], type: r[4]
  }));
  return ContentService
    .createTextOutput(JSON.stringify(items))
    .setMimeType(ContentService.MimeType.JSON);
}
LIST WIDGET

RemoteViewsServiceで「今日と明日の予定リスト」を表示

「リスト型ウィジェット」は、今日と明日の予定だけ を縦に並べる コレクションウィジェットです。AppWidgetProvider から RemoteViewsService を紐づけ、Room から予定を読み出して 1 行ずつ RemoteViews を返しています。

擬似コード:
dao.eventsBetween(todayStart, tomorrowEnd)
  .sortedBy { it.startAt }
  .map { toWidgetItem(it) }
TIMETABLE IMAGE

Bitmap/Canvasで「0〜24時」のタイムテーブル画像を描画

「1日タイムテーブル画像ウィジェット」は、 背景に 0〜24 時の時間軸を描き、その上に予定ブロックを長方形として重ねる 画像を生成します。 Bitmap.createBitmap() でキャンバスを作り、 1マス30分などのスケールで矩形を塗りつぶしていきます。

座標変換イメージ:
val totalMinutes = 24 * 60
val yStart = height * (eventStartMinutes / totalMinutes.toFloat())
val yEnd = height * (eventEndMinutes / totalMinutes.toFloat())
SYNC & UPDATE

同期ワーカーと0時更新でいつも新しいタイムテーブルに

GAS との同期は WorkManager or 自前の AlarmManager で定期実行し、 成功時に notifyAppWidgetViewDataChanged() や 画像再描画処理を呼んで、2種類のウィジェットを更新します。 深夜0時のタイミングには日付切り替え用の更新も走らせ、 「昨日の予定が残ったウィジェット」が画面に残らないようにしています。

実験・結果・課題

「文字だけの予定」から「図で見える予定」へ

最初はリスト型ウィジェットだけを作り、 文字で予定を確認していましたが、 実験として 1 日のタイムテーブルを画像で描画したところ、 「この時間帯は絶対に埋めたくない」など感覚的な判断が しやすくなる手応えがありました。

リスト型ウィジェット
(a) リスト型ウィジェット
1日タイムテーブル画像ウィジェット
(b) 1日タイムテーブル画像ウィジェット

現在の課題

・GAS 側のレスポンス遅延や、ネットワーク無しのときの扱い ・ウィジェットのサイズが小さい端末での可読性 ・タイムテーブル画像の解像度と描画コストのバランス といった点にまだ課題があります。

オフライン時の挙動は暫定 ウィジェットのレスポンス改善を検討中

動画リンク

GAS から予定を同期し、リスト型ウィジェットと 1日タイムテーブル画像ウィジェットが更新される一連の流れを 撮影したデモ動画です。

GAS同期 → 2種類のウィジェット更新デモ

まだできていない部分と今後

「スプレッドシートの予定」から「生活ダッシュボード」へ

widget アプリは、GAS を中心に据えた 「予定の一元管理」の最初の一歩です。 将来的には、Rootine の遂行率や EEG の集中度、 タスクの重要度なども同じ GAS/シートに集約し、 ホーム画面のウィジェットが日々のダッシュボードになることを目指しています。

  • 短期: GAS 側のレスポンス最適化・キャッシュ層の導入。
  • 中期: 予定以外(タスク・習慣・EEG指数)をタイムテーブルに重ねて描画。
  • 長期: 複数ウィジェットをまとめた「1画面ダッシュボード」の構築。

これからの改善点と開発計画

同期の安定化・ウィジェットの見やすさ・他データとの統合の 3つの観点で改善を進めます。

  • Sync: リトライ・バックオフ・ローカルキャッシュの強化。
  • Design: 色分け・ラベル・凡例の追加でタイムテーブルを読みやすく。
  • Integration: RootineWidget / EEGWidget との連携調査。
17
18
19
20
21
22
23
24
25
26
27
28
29
30
GASレスポンス最適化・同期安定化
タイムテーブル描画のデザイン改善
Rootine / EEGとの統合設計

このプロジェクトを通して

感じていること

GAS と Android ウィジェットを組み合わせることで、 「自分だけの予定サーバ」と「好きな形の表示」をつなげられることが分かりました。 スプレッドシートという身近なツールを起点にしつつ、 ホーム画面にはかなり自由度の高い UI を出せるのが面白いと感じています。

一方で、ウィジェットは制約も多く、 キャッシュ戦略や描画コスト、更新タイミングなどを丁寧に設計しないと すぐに「重い」「バッテリーを食う」アプリになってしまうことも学びました。 この経験は、今後 Rootine や EEG 関連のウィジェットを作るときにも 活かしていきたいです。

今の自分へのメモ

  • ・GAS側のスクリプトとシート構造は必ず図に残しておくこと。
  • ・タイムテーブル画像の座標変換ロジックは、他プロジェクトでも再利用できる形で整理すること。

リファレンス & リンク

リファレンス

  • Google Apps Script – Web Apps
  • Google Apps Script – Spreadsheet Service
  • Android Developers – App Widgets
  • Android Developers – Canvas & Drawables

リンク