前回の続きからと言いたいところだが、諸事情により中身をガラッと変え、作り直すことになった。
作っていくものは全国のナイトクラブ(のイベントスケジュール)をリストで表示するアプリ。
プラスで以下の機能を追加実装する。
・MVVMで作る
・現在の位置情報から周辺を探す機能
・お気に入り機能
スケジュールに関しては全国のナイトクラブのHPのスケジュールのURLを全てかき集めてDBにまとめてAPIとして引っ張ってきてwebviewで表示させる。
画面構成に関してはお気に入り一覧を増やすくらいであとはほぼ据え置き。
都道府県の絞り込み
今回の大幅な変更に伴って、気になるWidgetも見つけたので使ってみるのと、冗長だと思っていたコード(見ないフリしてた)の修正をする。
FilterChip
という視覚的にわかりやすく、感覚的に楽しい、今回のような絞り込みにうってつけのwidgetを採用した。
コードの修正だが、都道府県のリストの表示部分がかなりダラダラ書かれていたのでスッキリさせていく。
modelにfilterList
を用意する。
都道府県がタップされたとき(on/off)にselectPrefecture
でfilterList
にその都道府県を追加、削除する。
final areaProvider =
StateNotifierProvider<AreaNotifier, AreaState>(
(ref) => AreaNotifier(),
);
class AreaNotifier extends StateNotifier<AreaState> {
AreaNotifier(): super(AreaState());
void selectPrefecture(String prefecture, bool selected){
if(selected){
state = state.copyWith(filterList: [...state.filterList, prefecture]);
} else {
state = state.copyWith(filterList: [for(final area in state.filterList)
if(area != prefecture) area,
]);
}
}
void clearFilter(bool value){
if(!value) state = state.copyWith(filterList: []);
}
選択中の都道府県がある場合、ExpansionTileを開いた状態で表示させてわかりやすくした。
また、冗長だった部分も都道府県リストをMapで管理して.mapメソッドを使うことでかなりスッキリした。
const List<String> areaList = ['北海道・東北', '関東', '中部', '関西', '中国', '四国', '九州'];
const List<String> touhokuList = ['北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県'];
const List<String> kantouList = ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'];
const List<String> chuubuList = ['新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県', '静岡県', '愛知県', '三重県'];
const List<String> kansaiList = ['滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県'];
const List<String> chuugokuList = ['鳥取県', '島根県', '岡山県', '広島県', '山口県'];
const List<String> shikokuList = ['徳島県', '香川県', '愛媛県', '高知県'];
const List<String> kyuushuuList = ['福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県', '鹿児島県', '沖縄県'];
//Mapにした!
Map<String, List<String>> allAreaList = {'北海道・東北' : touhokuList, '関東' : kantouList, '中部' : chuubuList, '関西' : kansaiList, '中国' : chuugokuList, '四国' : shikokuList, '九州' : kyuushuuList};
Wrap(
children: allAreaList.keys.map((key) => ExpansionTile(
title: Text(key),
initiallyExpanded: areaState.filterList.isEmpty ? false : allAreaList[key]!.join().contains(areaState.filterList[0]) ? true : false,
onExpansionChanged: (value) {
areaNotifier.clearFilter(value);
},
children: [
Column(
children: [
Wrap(
spacing: 8.0,
children:
allAreaList[key]!.map((prefecture) => FilterChip(
backgroundColor: Colors.grey,
selectedColor: Colors.orange.shade300,
label: Text(prefecture),
selected: areaState.filterList.contains(prefecture),
onSelected: (selected){
areaNotifier.selectPrefecture(prefecture, selected);
},
)).toList()
),
const SizedBox(height: 8,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: (){
areaNotifier.clearFilter(false);
},
child: const Text('リセット'),
),
ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: const Text('探す'),
),
],
),
const SizedBox(height: 8,)
],
),
],
)).toList()
),
位置情報
使用したパッケージはこちら↓
・geolocator
・geocoding
参考はこちら↓
geolocator
で取得した現在地の経度緯度をgeocoding
を使って実際の住所に変換するという流れ。
andoroid、iOSともに権限の設定が必要だが、上記のサイト通りで問題なく動いたので割愛。
位置情報を取得をしていく上でmodelとして持ちたい値を定義していく。
geolocator
に現在のアプリの位置情報の設定を確認するメソッドがあり、現在の設定状況によって状態を返してくれるのだが、「毎回確認する(基本的にはOFFだが必要があれば確認)」という状態が用意されておらず、nullがかえってくる。
そのため、基本的に設定の状態のデフォルトはOFF(LocationPermission.denied
)として保持することにした。
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
part 'area_state.freezed.dart';
@freezed
class AreaState with _$AreaState {
factory AreaState({
//アプリの位置情報の設定の状態
@Default(LocationPermission.denied) LocationPermission permission,
//取得した位置情報(住所に変換済み)
Placemark? location,
@Default([]) List<String> filterList,
}) = _AreaState;
}
あとはgeolocator
の位置情報をチェックするメソッド、取得するメソッド、geocoding
の住所に変換するメソッドを必要箇所で使うだけ。