개발이 좋아서/Flutter가 좋아서
[flutter] 미세먼지 앱_마무리_repository, screen, utils, main
zoaseo
2023. 2. 7. 17:38
1)
2) repository
/stat_repository.dart
import 'package:dio/dio.dart';
import 'package:dusty_dust/const/data.dart';
import 'package:dusty_dust/model/stat_model.dart';
class StatRepository {
static Future<List<StatModel>> fetchData(ItemCode itemCode) async {
final response = await Dio().get(
'http://apis.data.go.kr/B552584/ArpltnStatsSvc/getCtprvnMesureLIst',
queryParameters: {
'serviceKey': serviceKey,
'returnType': 'json',
'numOfRows': 30,
'pageNo': 1,
'itemCode': itemCode.name,
'dataGubun': 'HOUR',
'searchCondition': 'WEEK',
},
);
return response.data['response']['body']['items']
.map<StatModel>(
(item) => StatModel.fromJson(json: item),
)
.toList();
}
}
3) screen
/home_screen.dart
import 'package:dio/dio.dart';
import 'package:dusty_dust/container/category_card.dart';
import 'package:dusty_dust/container/hourly_card.dart';
import 'package:dusty_dust/component/main_app_bar.dart';
import 'package:dusty_dust/component/main_drawer.dart';
import 'package:dusty_dust/model/stat_model.dart';
import 'package:dusty_dust/repository/stat_repository.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:dusty_dust/const/regions.dart';
import 'package:dusty_dust/utils/data_utils.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String region = regions[0];
bool isExpanded = true;
ScrollController scrollController = ScrollController();
Future<void> fetchData() async {
try {
final now = DateTime.now();
final fetchTime = DateTime(
now.year,
now.month,
now.day,
now.hour,
);
final box = Hive.box<StatModel>(ItemCode.PM10.name);
if (box.values.isNotEmpty &&
(box.values.last as StatModel).dataTime.isAtSameMomentAs(fetchTime)) {
print('이미 최신 데이터가 있습니다.');
return;
}
List<Future> futures = [];
for (ItemCode itemCode in ItemCode.values) {
futures.add(
StatRepository.fetchData(
itemCode,
),
);
}
final results = await Future.wait(futures);
// Hive에 데이터 넣기
for (int i = 0; i < results.length; i++) {
// ItemCode
final key = ItemCode.values[i];
// List<StatModel>
final value = results[i];
final box = Hive.box<StatModel>(key.name);
for (StatModel stat in value) {
box.put(stat.dataTime.toString(), stat);
}
final allKeys = box.keys.toList();
if (allKeys.length > 24) {
// start - 시작 인덱스
// end - 끝 인덱스
// ['red', 'orange', 'yellow', 'green', 'blue']
// .sublist(1, 3)
// ['orange', 'yellow']
final deleteKeys = allKeys.sublist(0, allKeys.length - 24);
box.deleteAll(deleteKeys);
}
}
} on DioError catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'인터넷 연결이 원활하지 않습니다.',
),
),
);
}
}
scrollListener() {
bool isExpanded = scrollController.offset < 500 - kToolbarHeight;
if (isExpanded != this.isExpanded) {
setState(() {
this.isExpanded = isExpanded;
});
}
}
@override
void initState() {
super.initState();
scrollController.addListener(scrollListener);
fetchData();
}
@override
void dispose() {
scrollController.removeListener(scrollListener);
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Box>(
valueListenable: Hive.box<StatModel>(ItemCode.PM10.name).listenable(),
builder: (context, box, widget) {
if (box.values.isEmpty) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
// PM10 (미세먼지)
// box.value.toList().last
final recentStat = box.values.toList().last as StatModel;
final status = DataUtils.getStatusFromItemCodeAndValue(
value: recentStat.getLevelFromRegion(region),
itemCode: ItemCode.PM10,
);
return Scaffold(
drawer: MainDrawer(
region: region,
onRegionTap: (String region) {
setState(() {
this.region = region;
});
Navigator.of(context).pop();
},
lightColor: status.lightColor,
darkColor: status.darkColor,
),
body: Container(
color: status.primaryColor,
child: RefreshIndicator(
onRefresh: () async {
await fetchData();
},
child: CustomScrollView(
controller: scrollController,
slivers: [
MainAppBar(
isExpanded: isExpanded,
stat: recentStat,
status: status,
region: region,
),
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CategoryCard(
region: region,
lightColor: status.lightColor,
darkColor: status.darkColor,
),
const SizedBox(height: 16.0),
...ItemCode.values.map(
(itemCode) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: HourlyCard(
darkColor: status.darkColor,
lightColor: status.lightColor,
region: region,
itemCode: itemCode,
),
);
},
).toList(),
const SizedBox(height: 16.0),
],
),
)
],
),
),
),
);
},
);
}
}
4) utils
/data_utils.dart
import 'package:dusty_dust/const/status_level.dart';
import 'package:dusty_dust/model/stat_model.dart';
import 'package:dusty_dust/model/status_model.dart';
class DataUtils {
static String getTimeFromDateTime({required DateTime dateTime}) {
return '${dateTime.year}-${dateTime.month}-${dateTime.day} ${getTimeFormat(dateTime.hour)}:${getTimeFormat(dateTime.minute)}';
}
static String getTimeFormat(int number) {
return number.toString().padLeft(2, '0');
}
static String getUnitFromItemCode({required ItemCode itemCode}) {
switch (itemCode) {
case ItemCode.PM10:
return '㎍/m³';
case ItemCode.PM25:
return '㎍/m³';
default:
return 'ppm';
}
}
static String getItemCodeKrString({
required ItemCode itemCode,
}) {
switch (itemCode) {
case ItemCode.PM10:
return '미세먼지';
case ItemCode.PM25:
return '초미세먼지';
case ItemCode.NO2:
return '이산화질소';
case ItemCode.O3:
return '오존';
case ItemCode.CO:
return '일산화탄소';
case ItemCode.SO2:
return '아황산가스';
}
}
static StatusModel getStatusFromItemCodeAndValue({
required double value,
required ItemCode itemCode,
}) {
return statusLevel.where(
(status) {
if (itemCode == ItemCode.PM10) {
return status.minFineDust < value;
} else if (itemCode == ItemCode.PM25) {
return status.minUltraFineDust < value;
} else if (itemCode == ItemCode.NO2) {
return status.minNO2 < value;
} else if (itemCode == ItemCode.O3) {
return status.minO3 < value;
} else if (itemCode == ItemCode.SO2) {
return status.minSO2 < value;
} else if (itemCode == ItemCode.CO) {
return status.minCO < value;
} else {
throw Exception('알 수 없는 ItemCode입니다.');
}
},
).last;
}
}
5) main.dart
import 'package:dusty_dust/model/stat_model.dart';
import 'package:dusty_dust/screen/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
const testBox = 'test';
void main() async {
await Hive.initFlutter();
Hive.registerAdapter<StatModel>(StatModelAdapter());
Hive.registerAdapter<ItemCode>(ItemCodeAdapter());
await Hive.openBox(testBox);
for(ItemCode itemCode in ItemCode.values){
await Hive.openBox<StatModel>(itemCode.name);
}
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
fontFamily: 'sunflower',
),
home: HomeScreen(),
),
);
}