728x90

 

0. pubspec.yaml 에 기초 프로젝트 설정하기

name: culture_app
description: A new Flutter project.
publish_to: 'none' 
version: 1.0.0+1
environment:
  sdk: '>=3.0.0 <4.0.0'
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  flutter_screenutil: ^5.8.4
  url_launcher: ^6.1.12
  cached_network_image: ^3.2.3
  flutter_dotenv: ^5.1.0
  font_awesome_flutter: ^10.5.0
  dio: ^5.2.1+1
  awesome_dialog: ^2.2.1
  shimmer: ^3.0.0
  intl: any
  flutter_riverpod: ^2.3.6
  riverpod: ^2.3.6  
  go_router: ^9.0.3
  connectivity_plus: ^4.0.1
  pretty_dio_logger: ^1.3.1
  riverpod_annotation: ^2.1.1
  shared_preferences: ^2.2.0
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1
  lottie: ^2.4.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  flutter_localizations:
    sdk: flutter
  freezed: ^2.4.1
  build_runner: ^2.4.6
  flutter_launcher_icons: ^0.13.1
  json_serializable: ^6.7.1

flutter:
  uses-material-design: true
  fonts:  
    - family: 'NotoSansKR'
      fonts:  
        - asset: assets/fonts/NotoSansKR/NotoSansKR-Regular.otf
        - asset: assets/fonts/NotoSansKR/NotoSansKR-Medium.otf
          weight: 500
        - asset: assets/fonts/NotoSansKR/NotoSansKR-Bold.otf
          weight: 700
        - asset: assets/fonts/NotoSansKR/NotoSansKR-Black.otf
          weight: 900
  assets:
    - assets/images/
    - assets/lotties/
    # - assets/images/common/
    # - assets/images/icon/
    # - assets/config/
    - assets/fonts/NotoSansKR/

 

1. main에 ProviderScope 설정하기

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

 

2. GoRouter로 화면 지정하기

import 'dart:convert';

import 'package:culture_app/detail_screen.dart';
import 'package:culture_app/home_screen.dart';
import 'package:culture_app/model/festival_model.dart';
import 'package:culture_app/onboarding_screen.dart';
import 'package:go_router/go_router.dart' show GoRoute, GoRouter;

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const OnboardingScreen(),
    ),
    GoRoute(
      path: '/home',
      builder: (context, state) =>
          const HomeScreen(title: 'Flutter Demo Home Page'),
    ),
    GoRoute(
      path: '/detail',
      builder: (context, state) {
        FestivalModel festivalModel =
            FestivalModel.fromJson(state.extra as Map<String, dynamic>);
        return DetailScreen(
          festivalModel: festivalModel,
        );
      },
    ),
  ],
);

 

3. http 클라이언트 코드 작성하기

import 'dart:convert';

import 'package:culture_app/model/festival_model.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<List<FestivalModel>> getFestivalList(int year) async {
  debugPrint('httpClient loading page $year');
  // year = 20230711;
  try {
    final response =
        await http.get(Uri.parse('http://3.38.247.254:3060/festivals/$year'));
    print('year');
    print(year);
    // print response
    print('response.body');
    print(response.body);
    final List<FestivalModel> festivalList =
        (jsonDecode(response.body)['data'] as List)
            .map((e) => FestivalModel.fromJson(e))
            .toList();
    return festivalList;
  } catch (ex, st) {
    debugPrint(ex.toString());
    debugPrint(st.toString());
    return [];
  }
}

 

4. Riverpod :: 축제 리스트 페이징을 위한 state, provider, notifier 선언

import 'dart:async';

import 'package:culture_app/model/festival_model.dart';
import 'package:culture_app/http_client.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'festival_provider.freezed.dart';

// state, provider, notifier

// festival 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._();
}

// festival provider
final festivalProvider =
    StateNotifierProvider<FestivalNotifier, FestivalState>((ref) {
  return FestivalNotifier();
});

// festival notifier
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);
  }
}

 

5. Riverpod :: 인덱스 값 관리를 위한 state, provider, notifier 선언

import 'dart:async';

import 'package:culture_app/model/festival_model.dart';
import 'package:culture_app/http_client.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'onboarding_index_provider.freezed.dart';

@freezed
abstract class OnboardingIndexState with _$OnboardingIndexState {
  const factory OnboardingIndexState({
    @Default(0) int pageIndex,
  }) = _OnboardingIndexState;
  const OnboardingIndexState._();
}

final onboardingIndexProvider =
    StateNotifierProvider<OnboardingIndexNotifier, OnboardingIndexState>((ref) {
  return OnboardingIndexNotifier();
});

class OnboardingIndexNotifier extends StateNotifier<OnboardingIndexState> {
  OnboardingIndexNotifier() : super(OnboardingIndexState()) {
    _initFestival();
  }
  // 초기화
  _initFestival() async {}

  // pageIndex를 index 파라미터로 변경하는 함수
  void changePageIndex(int index) {
    state = state.copyWith(pageIndex: index);
  }
}

 

6. freezed 빌드를 위한 freezed.sh 쉘 스크립트

flutter pub run build_runner build --delete-conflicting-outputs

 

 

+ Recent posts