0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NCMBのFlutter SDKを使ってカレンダーアプリを作る(その3:予定の登録と更新、削除)

Posted at

NCMBのFlutter SDKを使ってカレンダーアプリを作ります。予定を登録したり、カレンダーライブラリ(table_calendar)を使って予定を表示できるというアプリです。

前回の記事では認証処理を実装しましたので、今回は予定の表示と作成・更新、そして削除処理を実装していきます。

コードについて

今回のコードはNCMBMania/flutter-calendarにアップロードしてあります。実装時の参考にしてください。

カレンダーの予定一覧について

画面について

localhost_56942_ (2).png

カレンダー画面は以下のようになります。カレンダーの表示自体はtable_calendarを利用しています。

@override
Widget build(BuildContext context) {
	return Scaffold(
			appBar: AppBar(
				title: const Text("カレンダー"),
				actions: [
					IconButton(
						icon: const Icon(Icons.add),
						onPressed: _addSchedule,
					)
				],
			),
			body: Column(children: [
				TableCalendar(
					locale: 'ja',
					eventLoader: _eventLoader,
					firstDay: DateTime.utc(2010, 10, 16, 10),
					lastDay: DateTime.utc(2030, 3, 14),
					focusedDay: _focusDate,
					onDaySelected: _onDaySelected,
				),
				_selectedSchedules.isEmpty
						? const Text("該当日にイベントはありません")
						: Expanded(
								child: ListView.builder(
										itemCount: _selectedSchedules.length,
										itemBuilder: (BuildContext context, int index) {
											final schedule = _selectedSchedules[index];
											return ScheduleListPage(
												schedule: schedule,
												onEdited: (NCMBObject schedule) {
													setState(() {
														_selectedSchedules[index] = schedule;
													});
												},
											);
										}))
			]));
}

予定の取得とカレンダーへの設定

画面を表示したタイミングでNCMBのデータストアから予定一覧を取得しています。デフォルトでは表示月の月初から月末までの予定をすべて取得し、それを _schedules へセットします。

void _getSchedule() async {
	// 予定検索用クエリー
	var query = NCMBQuery("Schedule");
	// 基準日時
	var date = DateTime.now();
	// 月初
	DateTime startDate = DateTime(date.year, date.month, 1);
	// 翌月初
	DateTime endDate =
			DateTime(date.year, date.month + 1, 1).add(const Duration(days: -1));
	// 検索条件を設定
	query
		..greaterThanOrEqualTo("startDate", startDate)
		..lessThan("endDate", endDate);
	// 検索実行
	var schedules =
			(await query.fetchAll()).map((s) => s as NCMBObject).toList();
	// 表示に反映
	setState(() {
		_schedules = schedules;
	});
}

カレンダー表示

カレンダーはtable_calendarで行っていますが、予定があるところにはドットが表示されます。以下はその該当部分のコードです。最大4つまでの表示で、それ以上を返しても結果は変わりませんでした。

// イベントの有無を返す関数
List<NCMBObject> _eventLoader(DateTime day) {
	// 日付毎に呼ばれる
	return _filterdSchedule(day, updateSelected: false);
}

// ある日付に存在するスケジュールを一覧で返す関数
List<NCMBObject> _filterdSchedule(DateTime day, {updateSelected = true}) {
	// 日付を文字列にするフォーマッタ
	final dateFormat = DateFormat('yyyy/MM/dd');
	final targetDate = dateFormat.format(day);
	// フィルタリング
	final schedules = _schedules
			.where((schedule) =>
					dateFormat.format(schedule.getDateTime("startDate",
							defaultValue: DateTime.now())) ==
					targetDate)
			.toList();
	// 一覧表示の場合はtrue
	if (updateSelected) {
		setState(() {
			_selectedSchedules = schedules;
		});
	}
	return schedules;
}

日付を選択後、予定を一覧表示する

カレンダーの日付を選択したら、該当日の予定を一覧表示します。まず該当日だけのデータにフィルタリングします。

// 日付を選択した際の処理
void _onDaySelected(selectedDay, focusedDay) {
	// フォーカス日を更新
	setState(() {
		_focusDate = selectedDay;
	});
	// フィルタリングを実行
	_filterdSchedule(selectedDay);
}

そして、この結果をListで表示します。

Expanded(
	child: ListView.builder(
			itemCount: _selectedSchedules.length,
			itemBuilder: (BuildContext context, int index) {
				final schedule = _selectedSchedules[index];
				return ScheduleListPage(
					schedule: schedule,
					onEdited: (NCMBObject schedule) {
						setState(() {
							_selectedSchedules[index] = schedule;
						});
					},
				);
			}))

ScheduleListPage は受け取ったスケジュールを描画します。そして、該当予定をタップすると、編集画面(ScheduleFormPage)に遷移します。

@override
Widget build(BuildContext context) {
	return GestureDetector(
      onTap: _onTap,
			child: Container(
					decoration: const BoxDecoration(
						border: Border(
							bottom: BorderSide(color: Colors.black38),
						),
					),
					child: Column(
						children: [
							Row(
								children: [
									Text(widget.schedule
											.getString("title", defaultValue: "タイトルなし")),
									const Spacer(),
									Text(_viewTime())
								],
							),
							Container(
									alignment: Alignment.centerLeft,
									child: Text(
											widget.schedule.getString("body", defaultValue: "")))
						],
					)));
}

タップして予定を編集した際には、返却値として編集後のスケジュールが返ってきます。そのデータをスケジュール一覧と差し替えるため、 onEdited を呼び出します。

void _onTap() async {
	var schedule = await Navigator.push(
			context,
			MaterialPageRoute(
					settings: const RouteSettings(name: '/new'),
					builder: (BuildContext context) =>
							ScheduleFormPage(schedule: widget.schedule)));
	if (schedule != null) {
		widget.onEdited(schedule);
	}
}

予定を追加・編集する

予定を追加したり、編集する際には ScheduleFormPage へ移動します。新規作成の場合は CalendarPage のナビゲーションーメニューにあるプラスアイコンより遷移します。

appBar: AppBar(
	title: const Text("カレンダー"),
	actions: [
		IconButton(
			icon: const Icon(Icons.add),
			onPressed: _addSchedule,
		)
	],
),

_addScheduleは画面遷移し、結果として作成したスケジュールが返ってきます。そのスケジュールをリストに追加します。

void _addSchedule() async {
	var schedule = await Navigator.push(
			context,
			MaterialPageRoute(
					settings: const RouteSettings(name: '/new'),
					builder: (BuildContext context) =>
							ScheduleFormPage(schedule: NCMBObject("Schedule"))));
	if (schedule != null) {
		_schedules.add(schedule as NCMBObject);
	}
}

フォーム画面について

フォーム画面(ScheduleFormPage)は予定のタイトルと詳細、予定開始日時、終了日時などを入力して登録を行います。

@override
Widget build(BuildContext context) {
	return Scaffold(
			appBar: AppBar(
				title: const Text("予定の追加・編集"),
			),
			body: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
				Column(mainAxisAlignment: MainAxisAlignment.center, children: [
					SizedBox(
							height: 50,
							width: 250,
							child: TextFormField(
								initialValue: _title,
								decoration: const InputDecoration.collapsed(hintText: 'タイトル'),
								onChanged: (value) {
									setState(() {
										_title = value;
									});
								},
							)),
					SizedBox(
							height: 50,
							width: 250,
							child: TextFormField(
								initialValue: _body,
								decoration: const InputDecoration.collapsed(hintText: '予定詳細'),
								maxLines: 10,
								onChanged: (value) {
									setState(() {
										_body = value;
									});
								},
							)),
					SizedBox(
							height: 50,
							width: 250,
							child: TextFormField(
									controller: _startDateController,
									decoration:
											const InputDecoration.collapsed(hintText: '開始日時'),
									maxLines: 10,
									enableInteractiveSelection: false,
									onTap: () => _showDateTimePicker("startDate"))),
					SizedBox(
							height: 50,
							width: 250,
							child: TextFormField(
									controller: _endDateController,
									decoration:
											const InputDecoration.collapsed(hintText: '終了日時'),
									maxLines: 10,
									enableInteractiveSelection: false,
									onTap: () => _showDateTimePicker("endDate"))),
					TextButton(onPressed: _save, child: const Text("保存する")),
				])
			]));
}

データの準備

NCMBObjectは編集にそのまま使えないので、画面表示を行ったタイミングで入力用変数に適用します。

@override
void initState() {
	setState(() {
		_title = widget.schedule.getString("title", defaultValue: "");
		_body = widget.schedule.getString("body", defaultValue: "");
		if (widget.schedule.objectId == null) {
			_endDate = _startDate.add(const Duration(hours: 1));
		} else {
			_startDate = widget.schedule
					.getDateTime("startDate", defaultValue: DateTime.now());
			_endDate = widget.schedule
					.getDateTime("endDate", defaultValue: DateTime.now());
		}
		_startDateController.text = _dateFormat.format(_startDate);
		_endDateController.text = _dateFormat.format(_endDate);
	});
	super.initState();
}

データの保存

データを新規作成・更新する流れは同じです。入力値の適用と、ACL(アクセス権限)を設定します。ACLはデータをセキュアに扱うために必要です。今回はデータを作成した本人のみ、読み書きできるようにします。

void _save() async {
	// 入力データを適用
	widget.schedule
		..set("title", _title)
		..set("body", _body)
		..set("startDate", _startDate)
		..set("endDate", _endDate);
	// ACL(アクセス権限)を設定
	var acl = NCMBAcl();
	var user = await NCMBUser.currentUser();
	acl
		..setUserReadAccess(user!, true)
		..setUserWriteAccess(user, true);
	widget.schedule.set("acl", acl);
	// 保存実行
	await widget.schedule.save();
	// 前の画面に戻る
	Navigator.pop(context, widget.schedule);
}

まとめ

ここまでの流れでカレンダーアプリの完成です。NCMBのデータストアでは日付や文字列(他にも数字や配列、真偽値、オブジェクト、位置情報)など様々な形式でデータの保存・取得ができます。ぜひ柔軟に皆さんのアプリに活かしてください。

mBaaSでサーバー開発不要! | ニフクラ mobile backend

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?