728x90
1. Riverpod의 장점 특징
1.1. 안전한 컴파일
Provider를 사용할 때 처럼 더 이상 ProviderNotFoundException 예외가 발생하지 않고, 로딩 상태를 처리하는 것을 걱정하지 않아도 됩니다. Riverpod를 사용하면 코드가 컴파일되어 작동합니다.
1.2. 제한없는 Provider
Riverpod는 Provider에서 영감을 얻었지만 동일한 유형의 여러 Provider를 지원하는 것과 같은 주요 문제 중 일부를 해결합니다. 비동기 Provider를 기다리고 있습니다. 어디에서나 Provider를 추가할 수 있습니다.
1.3. Flutter에 의존하지 않습니다.
Flutter에 의존하지 않고 Provider를 생성/공유/테스트합니다. 여기에는 BuildContext 없이 Provider를 수신할 수 있는 기능이 포함됩니다.
2. riverpod 패키지 추가하기
dependencies:
flutter:
sdk: flutter
riverpod: ^2.3.10
3. main() 메서드에 ProviderScrope 지정하기
앱 전체에 대한 공통 상태를 관리할 수 있게 됩니다. 앱의 최상위 레벨에서 상태를 제공하기 때문에, 앱의 어느 곳에서나 상태에 액세스하거나 상태를 변경할 수 있습니다.
void main() {
runApp(const ProviderScope(child: MyApp()));
}
4. Model 만들기 (freezed)
import 'package:freezed_annotation/freezed_annotation.dart';
part 'festival_model.freezed.dart';
part 'festival_model.g.dart';
@freezed
class FestivalModel with _$FestivalModel {
const factory FestivalModel({
required int? SEQ_NO,
required int? ALL_KWRD_RANK_CO,
required String? SRCHWRD_NM,
required String? UPPER_CTGRY_NM,
required String? LWPRT_CTGRY_NM,
required int? MOBILE_SCCNT_VALUE,
required int? PC_SCCNT_VALUE,
required int? SCCNT_SM_VALUE,
required int? SCCNT_DE,
required String? imageLink,
}) = _FestivalModel;
// '일련번호',
// '전체키워드순위수', v
// '검색어명',
// '상위카테고리명',
// '하위카테고리명', v
// '모바일검색량값',
// 'PC검색량값',
// '검색량합계값', v
// '검색량일자'
// ALL_KWRD_RANK_CO UPPER_CTGRY_NM LWPRT_CTGRY_NM
// SCCNT_SM_VALUE
factory FestivalModel.fromJson(Map<String, Object?> json) =>
_$FestivalModelFromJson(json);
}
5. State, StateNotifierProvider, Notifier 만들기
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'festival_provider.freezed.dart';
// 1. State 선언하기
@freezed
abstract class FestivalState with _$FestivalState {
const factory FestivalState({
@Default(20230711) int page,
List<FestivalModel>? festivalList,
@Default(true) bool isLoading,
@Default(false) bool isLoadMoreError,
@Default(false) bool isLoadMoreDone,
}) = _FestivalState;
const FestivalState._();
}
// 2. StateNotifierProvider 선언하기 - 3번의 Notifier, 1번의 State 넣어서 생성한다.
final festivalProvider =
StateNotifierProvider<FestivalNotifier, FestivalState>((ref) {
return FestivalNotifier();
});
// 3. Notifier 선언하기 - 1번의 State를 넣어서 생성한다.
class FestivalNotifier extends StateNotifier<FestivalState> {
FestivalNotifier() : super(const FestivalState()) {
_initFestival();
}
// 초기화
_initFestival([int? initPage]) async {
final page = initPage ?? state.page;
final festivalList = await getFestivalList(page);
if (festivalList == null) {
state = state.copyWith(page: page, isLoading: false);
return;
}
debugPrint('get festival is ${festivalList.length}');
state = state.copyWith(
page: page, isLoading: false, festivalList: festivalList);
}
// 추가로 불러오기
loadMoreFestival() async {
StringBuffer bf = StringBuffer();
bf.write('try to request loading ${state.isLoading} at ${state.page - 1}');
if (state.isLoading) {
bf.write(' fail');
return;
}
bf.write(' success');
debugPrint(bf.toString());
state = state.copyWith(
isLoading: true,
isLoadMoreDone: false,
isLoadMoreError: false,
);
final festivalList = await getFestivalList(state.page - 1);
if (festivalList == null) {
// error
state = state.copyWith(isLoadMoreError: true, isLoading: false);
return;
}
debugPrint(
'load more ${festivalList.length} posts at page ${state.page - 1}');
if (festivalList.isNotEmpty) {
// if load more return a list not empty, => increment page
state = state.copyWith(
page: state.page - 1,
isLoading: false,
isLoadMoreDone: festivalList.isEmpty,
festivalList: [...state.festivalList!, ...festivalList]);
} else {
// not increment page
state = state.copyWith(
isLoading: false,
isLoadMoreDone: festivalList.isEmpty,
);
}
}
// 새로고침
Future<void> refresh() async {
_initFestival(20230711);
}
}
Notifier의 내부 state 변수를 활용해 FestivalState의 내부 값을 읽거나 변경할 수 있다. 변경할 때는 copyWith() 함수를 사용할 수 있다.
state = state.copyWith(
page: state.page - 1,
isLoading: false,
isLoadMoreDone: fetchedFestivalList.isEmpty,
festivalList: [...state.festivalList!, ...fetchedFestivalList],
);
리스트에 값을 추가할 때는 위와 같은 방식으로 추가할 수 있다.
6. Screen - ConsumerStatefulWidget 상속, PageState - ConsumerState 상속(ref 사용 가능)
// 1. ConsumerStatefulWidget 상속
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
// 2. ConsumerState 상속
class _MyHomePageState extends ConsumerState<HomeScreen> {
ScrollController _controller = ScrollController();
int oldLength = 0;
@override
void initState() {
super.initState();
_controller.addListener(() async {
// debugPrint('pixel is ${_controller.position.pixels}');
// debugPrint('max is ${_controller.position.maxScrollExtent}');
if (_controller.position.pixels >
_controller.position.maxScrollExtent -
MediaQuery.of(context).size.height) {
if (oldLength == ref.read(festivalProvider).festivalList!.length) {
// 이전 데이터 로딩이 완료되었는지 확인하는 로직.
// 이전 loadMoreFestival() 호출로 인해 데이터가 이미 업데이트되었다면
// 새로운 loadMoreFestival() 호출을 하지 않도록 합니다.
// 이는 불필요한 API 호출이나 연산을 줄이는 데 도움이 됩니다.
ref.read(festivalProvider.notifier).loadMoreFestival();
}
}
});
}
@override
Widget build(BuildContext context) {
// festivalProvider가 사용한 FestivalState를 읽어옵니다.
// state.isLoading, state.festivalList와 같은 형태로 값을 읽을 수 있다.
final state = ref.watch(festivalProvider);
return Scaffold(
body: RefreshIndicator(
onRefresh: () {
// notifier 가져오기 - 새로고침
return ref.read(festivalProvider.notifier).refresh();
},
child: festivalList == null || festivalList.isEmpty ?
const Center(
child: Text('error'),
);
: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
controller: _controller,
itemCount: festivalList.length + 1,
itemBuilder: (ctx, index) {
if (index == state.festivalList.length) {
if (state.isLoadMoreError) {
return const Center(
child: Text('에러가 발생했습니다!'),
);
}
if (isLoadMoreDone) {
return const Center(
child: Text(
'조회가 완료되었습니다!',
style: TextStyle(color: Colors.green, fontSize: 20),
),
);
}
return const LinearProgressIndicator();
}
return FestivalListItem(
index: index,
festivalModel: festivalList[index],
);
}),
),
);
}
}
7. Notifier의 함수 호출
ref
.read(onboardingIndexProvider.notifier)
.changePageIndex(index);
'개발 > Flutter' 카테고리의 다른 글
카카오 로그인 통합하기: Android Key Hash와 iOS Bundle ID 문제 해결 가이드 (0) | 2023.12.13 |
---|---|
Flutter :: GoRouter (0) | 2023.08.29 |
Flutter :: Riverpod / GoRouter / state, provider, notifier 선언 / 페이징 / http (0) | 2023.08.14 |
Flutter :: Color 저장해서 변수로 재사용하기 (0) | 2023.08.14 |
Flutter :: TextStyle 저장하여 관리하기 (0) | 2023.08.14 |