no image
[Flutter/플러터] listview 안에서 scroll 하기
아래와 같은 Card가 나열된 listView를 만들었다 그런데 문제는 listview 안에서 저 회색 Container의 Text를 스크롤 하고 싶었지만, SingleChildScrollView를 써도 잘 되지 않았다 아래를 보면서 다시 해결해보자 다음과 같이 listview를 만들고 listview들을 스크롤하여 볼 수 있게 다음과 같이 설정해 준다. physics는 스크롤 상태를 설정한다. BouncingScrollPhysics()는 flutter에서 스크롤 했을 때 나타나는 그림자를 없앨 수 있다. scroll 방향과 shrinkWrap을 넣어주고.. ListView.separated( separatorBuilder: (BuildContext context, index){ return SizedB..
2022.10.14
[Flutter/플러터] Riverpod와 StateNotifier에 대해
stateNotifier는 changeNotifier의 업그레이드 버전이라고 볼 수 있다. 그럼 먼저 changeNotifier에 대해 알아보자.. ▶ ChangeNotifier 란? 변경사항을 알리고 싶을 때마다 리스너에게 알림을 제공하는 클래스 notifyListeners() 클래스에 변화가 있을 때 해당 메서드를 호출할 수 있다. ValueNotifier는 단일 값만을 전달하는 ChangeNotifier의 일종이다(오직 하나의 변경된 값만을 전달할 수 있다.) class MyValueNotifier extends ValueNotifier{ //하나의 값만을 갖고 있을 수 있음 int getValue() => value; } class MyChangeNotifier extends ChangeNotif..
2022.09.28
[Flutter/플러터] Riverpod 장점 및 사용법
getx를 사용하다가 프로젝트의 규모가 점점 더 커질 것 같아서, riverpod를 공부하기로 마음먹었다. getx를 사용했을 때, 코드가 간결해져서 생산성이 높고, global context로 어디에서나 객체 접근이 가능하지만 이것이 나중에는 단점이 될 수도 있다는 생각에 flutter에서 공식으로 밀고있는 provider와 그것의 개선안인 riverpod를 사용할 예정이다.. 일단 본격적으로 riverpod에 대해 알아보자.. Riverpod가 생겨난 배경 ▶ provider의 단점 provider를 사용하다 보면 결국에는 UI 코드와 섞이게 되는 문제를 볼 수 있다. main.dart class MyWidget extends StatelessWidget { @override Widget build(..
2022.09.28
no image
[Flutter/플러터] Riverpod를 통해 알아보는 플러터 아키텍처 - (2) Domain Model
2편은 Domain Model에 관한 것이다. 원글은 .. https://codewithandrea.com/articles/flutter-app-architecture-domain-model/ Flutter App Architecture: The Domain Model An introduction to the domain model and its role in defining entities and the business logic for manipulating them in the context of Flutter app architecture. codewithandrea.com Domain-Driven Design(DDD)에서 중요한 것은 model 이다. 좋은 도메인 모델을 갖고 있는 지의 여부는 ..
2022.09.27
no image
[Flutter/플러터] Riverpod를 통해 알아보는 플러터 아키텍처 - (1) Repository pattern
riverpod를 상태관리에 대해 자세히 알아보던 도중, 잘 정리되어 있는 글을 발견했다. 해당 글을 정리 및 번역한 글이며 원본 글은 아래에서 확인할 수 있다. https://codewithandrea.com/articles/flutter-repository-pattern/ Flutter App Architecture: The Repository Pattern An in-depth overview of the repository pattern in Flutter: what it is, when to use it, and various implementation strategies along with their tradeoffs. codewithandrea.com Riverpod 상태관리를 활용한 아키텍..
2022.09.27
no image
[Flutter/플러터] Freezed 플러그인 사용하기 - (1)
Freezed란? 데이터 클래스에 필요한 편의 기능들을 Code Generation으로 제공하는 라이브러리 아래 공식문서를 살펴보자 freezed | Dart Package Code generation for immutable classes that has a simple syntax/API without compromising on the features. pub.dev 사용 방법) 1. pubspec.yaml 파일에 build_runner와 Freezed를 설치한다. - 터미널에서 설치 flutter pub add freezed_annotation flutter pub add --dev build_runner flutter pub add --dev freezed # 만약 fromJson 혹은 toJs..
2022.09.27
[Flutter/플러터] getx를 사용하여 like 버튼(하트 버튼) 만들기
1. obs 변수를 만들어준다 var like = false.obs; 2. Obx 로 감싼 뒤, 위젯을 리턴해준다 Obx(() => CircleAvatar( backgroundColor: Colors.white, radius: 15, child: Center( child: IconButton( ... ), ), ), ), 3. 삼항 연산자를 사용해 조건에 알맞은 아이콘을 보여주도록 한다. product.like.value ? Icon(Icons.favorite_rounded) : Icon(Icons.favorite_border), 4. 눌렀을 때, 이벤트 처리 onPressed: () { product.like.toggle(); }, Obx(() => CircleAvatar( backgroundColor..
2022.09.23
[Flutter/플러터] Dart 반복문 쓰지 않고 리스트 합, 최댓값, 최솟값 구하기 - fold
반복문을 사용하지 않고 리스트의 총 합을 구할 수 있는 방법이 있다. fold ( )를 사용하면 된다. 공식 문서를 보자 https://api.dart.dev/stable/1.10.1/dart-core/List/fold.html fold method - List class - dart:core library - Dart API dynamic fold( initialValue,dynamic combine(previousValue, E element) ) Reduces a collection to a single value by iteratively combining each element of the collection with an existing value Uses initialValue as the ..
2022.09.23
320x100
728x90

 

 

 

아래와 같은 Card가 나열된 listView를 만들었다

 

 

 

그런데 문제는 listview 안에서 저 회색 Container의 Text를 스크롤 하고 싶었지만,

SingleChildScrollView를 써도 잘 되지 않았다

 

 

아래를 보면서 다시 해결해보자 

 

 

 


 

 

 

 

다음과 같이 listview를 만들고 

listview들을 스크롤하여 볼 수 있게 다음과 같이 설정해 준다.

 

 

 

 

 

 

physics스크롤 상태를 설정한다. 

BouncingScrollPhysics()는 flutter에서 스크롤 했을 때 나타나는 그림자를 없앨 수 있다. 

scroll 방향과 shrinkWrap을 넣어주고..

 


	ListView.separated(
                separatorBuilder: (BuildContext context, index){
                  return SizedBox(height: 10);
                },
                physics: BouncingScrollPhysics(),
                scrollDirection: Axis.vertical,
                shrinkWrap: true,
                itemCount: snapShot.data!.length,
                itemBuilder: (context, index) {
                  return MyCustomWidget(
                  
                     ...
                     
                    },
                  );
                },
              );

 

 

 

 

 

 

MyCustomWidget에서 SingleChildScrollViewExpanded 한다음, Column으로 감싸주면 된다.

 


	Container(
        height: height * 0.43,
        child: Card(
             ...
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Expanded(
                child: SingleChildScrollView(
                  physics: BouncingScrollPhysics(),
                  child: Container(
                    	...

 

 

 

그냥 SingleChildScrollView로 감싸주고 끝내지 말고 Expanded를 해주자!

 

 

 

 

 

 

728x90
반응형
320x100
728x90

 

 

stateNotifier는 changeNotifier의 업그레이드 버전이라고 볼 수 있다.

그럼 먼저 changeNotifier에 대해 알아보자..

 

 

 

 

▶   ChangeNotifier 란?

          변경사항을 알리고 싶을 때마다 리스너에게 알림을 제공하는 클래스

 

 

  • notifyListeners() 클래스에 변화가 있을 때 해당 메서드를 호출할 수 있다.
  • ValueNotifier단일 값만을 전달하는 ChangeNotifier의 일종이다(오직 하나의 변경된 값만을 전달할 수 있다.)

 

class MyValueNotifier extends ValueNotifier<int>{
  //하나의 값만을 갖고 있을 수 있음
  int getValue() => value;
}

class MyChangeNotifier extends ChangeNotifier{
  // multiple한 값들 가능 
}

 

 

 

이 두가지를 사용했을 때의 몇가지 문제점이 존재한다..

 

    1.  ChangeNotifier와 ValueNotifier는 mutable 이라 클래스 외부 누구나 notifier의 상태를 변경 가능하다. 

            →  이는 원하지 않은 상호작용을 야기할 수 있다.

 

    2.  ChangeNotifier를 사용하는 동안에는 notifyListeners()를 일일이 호출해야 한다.

 

 

 

class ItemNotifier extends ChangeNotifier {
  final List<String> _items = <String>[];
  int _size = 0;

  	...

  void add(String value) {
    _items.add(value);
    _size++;
    notifyListeners();
  }
  
}

 

 


 

 

 

이러한 단점들을 보완한 것이 StateNotifier이다 

 

▶  StateNotifier의  장점

  • 본질적으로 immutable 속성
  • 이전상태새로운 상태를 비교하며 이를 리스너에게 자동으로 알림
  • 단일 데이터의 수정 point 이다 

 

 

StateNotifier가 무엇인지 알았으니 이제 사용해보자..

 

먼저, pubspec.yaml 파일에 아래 두개의 dependency를 추가하자

// # pubspec.yaml

dependencies:
    state_notifier:
    flutter_state_notifier:

 

 

간단한 counter 클래스를 가지고 StateNotifier를 활용해볼 예정이다.

 

riverpod 패키지를 import 해 주고, StateNotifier 옆에 전달하고자 하는 변수의 형을 넣어준다

그 다음 그 밑에, StateCounter 인스턴스를 global 하게 선언해준다

 

 

counter.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';

class StateCounter extends StateNotifier<int> {
  StateCounter() : super(0);

  void increment() {
    state++;
  }

  void decrement() {
    state--;
  }
}

final StateCounter myCounter = StateCounter();

 

 

 

이제 StateNotifierBuilder를 사용해서 바뀐 값을 위젯에 적용하는 방법을 알아보겠다.

 

 

 

먼저 버튼 클릭을 통해 값을 증가해 줄테니, onpressed에 myCounter를 증가해보자..

floatingActionButton: FloatingActionButton(
        onPressed: () => myCounter.increment(),
        ...
),

 

 

 

다음으로는 변경하고자 하는 위젯을 StateNotifierBuilder로 감싸고

builder를 통해 값을 전달한다..

 body: StateNotifierBuilder(
        stateNotifier: myCounter,
        builder: (context, value, child) {
          return Center(
            child: Text(value.toString()),
          );
        },
      ),

 

 

 

 

최종 코드

import 'package:flutter/material.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
...

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...
      floatingActionButton: FloatingActionButton(
        // 1. 
        onPressed: () => myCounter.increment(),
        child: const Icon(Icons.add),
      ),
      body: StateNotifierBuilder(
        stateNotifier: myCounter,
        builder: (context, value, child) {
         // 2. 
          return Center(
            child: Text(value.toString()),
          );
        },
      ),
    );
  }
}

 

riverpod를 사용할 때, 물론 ChangeNotifier와 ValueNotifier를 사용할 수 있지만

StateNotifier를 사용하는 것을 권장하고 있다..

728x90
반응형
320x100
728x90

 

getx를 사용하다가 프로젝트의 규모가 점점 더 커질 것 같아서,

riverpod를 공부하기로 마음먹었다.

 

getx를 사용했을 때, 코드가 간결해져서 생산성이 높고,

global context로 어디에서나 객체 접근이 가능하지만 

이것이 나중에는 단점이 될 수도 있다는 생각에 flutter에서 공식으로 밀고있는 provider와 

그것의 개선안인 riverpod를 사용할 예정이다..

 

 

 


 

 

일단 본격적으로 riverpod에 대해 알아보자..

 

 

Riverpod가 생겨난 배경

    ▶   provider단점

 

  •   provider를 사용하다 보면 결국에는 UI 코드와 섞이게 되는 문제를 볼 수 있다.

 

main.dart

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (context) => MyFirstClass(),
      child: ProxyProvider<MyFirstClass, MySecondClass>(
        update: (context, firstClass, previous) => MySecondClass(firstClass),
        child: MyVisibleWidget(),
      ),
    );
  }
}

 

위의 코드를 보면 알 수 있다.. 

Provider로 위젯을 감싼 뒤 그 안에서 변화를 체크해야 하니 결국에는 UI와 복잡하게

얽혀있는 모습이다.. 

 

 

 

  • 타입에 의존하는 provider

 

main.dart

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (context) => 'A String far away.',
      child: Provider(
        create: (context) => 'A String that is close.',
        builder: (context, child) {
          return Text(Provider.of<String>(context));
        },
      ),
    );
  }
}

String 타입의 객체를 동일하게 제공하면 제일 가까운 놈에만 접근 가능하여

결국 A String far away의 값을 얻지 못한다..

 

 

 

그 외에도 여러번 접근하려면 그만큼의 선언이 필요하여 불편하고 제한적이다..

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: Container(),
)

해당과 같이 main에서 사용할 provider들에 대해 명시해 주어야 한다. 결국엔 파일을 왔다 갔다 해야 할 것이다.

 

 

 

 

 

이러한 단점들을 보완한 것이 riverpod 이다.

 Riverpod장점

  •  provider를 일단 한번 선언하면 어디에서든 global 접근이 가능하다 
final greetingProvider = Provider((ref) => 'Hello Riverpod!');

 

 

  하지만 provider 자체는 flutter 위젯 트리에 종속된다. 따라서

  전체 위젯트리를 ProviderScope으로 감쌀 필요가 있다.

 

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

 

 

 

  • ConsumerWidget의 사용

 

    이제 greetingProvider의 String 문자를 Text 위젯에 띄우는 방법에 대해 알아보자 

 

 

    1.  superclass를 ConsumerWidget으로 변경

class MyApp extends ConsumerWidget{
	...

 

 

  2. 위젯의 build 메소드에 ScopedReader function 추가

Widget build(BuildContext context, ScopedReader watch){
	...

 

 

  3. 위젯의 상태를 체크하여 rebuild 시켜주는 watch를 통해 provider 전달

final greeting = watch(greetingProvider);

 

 

 

전체 코드 

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final greeting = watch(greetingProvider);

    return MaterialApp(
      title: 'Riverpod Tutorial',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Riverpod Tutorial'),
        ),
        body: Center(
          child: Text(greeting),
        ),
      ),
    );
  }
}

 

 

 

오직 변화하는 Text만 감지를 해서 rebuild 해주고 싶을 때는 Consumer widget을 사용하면 된다.

 

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        ...
        body: Center(
          child: Consumer(
            builder: (context, watch, child) {
              final greeting = watch(greetingProvider);
              return Text(greeting);
            },
         ...
  }
}

 

 

 

  • AsyncValue의 사용으로 Future 처리를 더 쉽게 만들어준다.
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      		...
          child: Consumer(
            builder: (context, watch, child) {
              final responseAsyncValue = watch(responseProvider);
              return responseAsyncValue.map(
                data: (_) => Text(_.value),
                loading: (_) => CircularProgressIndicator(),
                error: (_) => Text(
                  _.error.toString(),
                  style: TextStyle(color: Colors.red),
                ),
              );
            ...
}

 

   map을 통해 data가 존재할 때, 데이터를 불러오는 중인 loading 상태일 때, 데이터를 불러올 수 없는 error 상태일 때

   각각 상태 처리를 한번에 가능하다. 

 

 

 

  • family를 통한 arguments 전달 
final greetingFamilyProvider = Provider.family<String, String>(
  (_, name) {
    return "Welcome, $name!";
  },
);

 

  family를 통해 String 인자를 넘겨주는 provider를 선언한다

 

sendArguments(WidgetRef ref) {
  ref.read(
    greetingFamilyProvider('yerim')
  );
}

 

  sendArguments 함수에서 greetingFamilyProvider에 name 인자값을 넘겨주면 된다.

 

 

 

 

  • autoDispose의 사용으로 자동으로 상태를 dispose 해줌

   

   fake request가 있을 때, 직접 dispose 해주지 않는 이상 계속 존재하였다. 

   하지만 autoDispose는 provider가 사용되지 않을 때, 알아서 dispose 해주는 역할을 한다. 

 

final responseProvider =
    FutureProvider.autoDispose.family<String, String>((ref, url) async {
  final httpClient = ref.read(fakeHttpClientProvider);
  return httpClient.get(url);
});

 


 

 

정리해보자면,

 

Riverpod

1. provider와 달리 compile time에 오류를 캐치할 수 있으며 (provider는 runtime에 오류가 난다..)

2. 동일한 유형의 여러 종속성을 주입할 수 있으며,

3. No BuildContext  즉, BuildContext가 더 이상 필요하지 않는다 

 

 

 

 

간단한 튜토리얼에 대해 알아보았다..

다음엔 더 깊게 들어가 보도록 하자

 

 

 

참고 : https://resocoder.com/

 

728x90
반응형
320x100
728x90

 

 

2편은 Domain Model에 관한 것이다.

원글은 ..

https://codewithandrea.com/articles/flutter-app-architecture-domain-model/

 

Flutter App Architecture: The Domain Model

An introduction to the domain model and its role in defining entities and the business logic for manipulating them in the context of Flutter app architecture.

codewithandrea.com

 

 


 

 

 

Domain-Driven Design(DDD)에서 중요한 것은 model 이다.

좋은 도메인 모델을 갖고 있는 지의 여부는 프로젝트의 성공과 실패를 결정하는 것과도 같다..

 

 

플러터 아키텍처를 다시 살펴보자

 

전에서 데이터 계층(Data layer)은 

외부 API 및 데이터 소스와 통신하는 역할을 하며 repository를 포함하고 있다고 말했다.

 

 

그 바로 위에 있는 모델 계층애플리케이션 계층모델들과 비즈니스 로직을 갖고 있으므로

매우 중요한 역할을 한다.

 

 

 

일단,

도메인 모델이란 무엇일까?

도메인 모델은 행동과 데이터를 모두 통합하는 도메인의 개념적 모델이다.

 

무슨 말인지 모르겠다면.. 그냥 도메인 자체를 이해하기 위한 개념 모델이라고 생각하자

 

 

· 데이터(Data) : 엔티티 집합과 그들의 관계로 이루어져 있음

· 행동(Behavior) : 비즈니스 로직을 변환한 것으로 엔티티들을 조작하는 역할을 함

 

 

아래는 엔티티 관계 예시이다

이커머스 앱 엔티티 및 관계

 

 

다음과 같이 작성된 다이어그램을 통해 

Model을 작성해 보도록 하자..

 

typedef ProductID = String;

class Product {
  Product({
    required this.id,
    required this.imageUrl,
    required this.title,
    required this.price,
    required this.availableQuantity,
  });

  final ProductID id;
  final String imageUrl;
  final String title;
  final double price;
  final int availableQuantity;

  // serialization code
  factory Product.fromMap(Map<String, dynamic> map, ProductID id) {
    ...
  }

  Map<String, dynamic> toMap() {
    ...
  }
}

 

model 안에는 UI를 그리기 위해 필요한 속성들은 필수적으로 가지고 있어야 한다.

아래 그림과 같이 품목에 대해 정보를 제공해주는 요소들을 말이다..

 

 

 

 

이렇게 simple data classes가 있는 반면, 

(위 product 모델은 리파지토리 및 서비스 혹은 다른 객체에 접근할 수 없는 그저 간단한 모델 클래스이다.)

 

비즈니스 로직을 갖고 있는 모델 클래스도 존재한다.

 

 


 

 

◆     비즈니스 로직을 가진 Model class

 

예를 들어서, 쇼핑 앱에서 피자와 여러 채소들을 구매하고자 장바구니에 담았다고 가정해보자.

장바구니에 담긴 항목들은 사용자의 선택에 따라 금액, 수량, 품목들을 갱신해야 한다.

 

 

이때, 장바구니를 갱신하는 코드를 상품 정보를 갖고있는 클래스 안에서 만들기 보단

해당 클래스를 플러터 확장 매서드인 extension을 사용하여 나타낸다.

 

 

 

상품 ID와 수량을 key-value 쌍의 Map 형태로 가지고 있는 Cart 클래스를 먼저 보자

class Cart {
  const Cart([this.items = const {}]);
  
  /// - key: product ID
  /// - value: quantity
  final Map<ProductID, int> items;

  factory Cart.fromMap(Map<String, dynamic> map) { ... }
  Map<String, dynamic> toMap() { ... }
}

 

cart에서 상품을 추가하거나 제거할 수도 있다.

해당 기능을 cart 클래스 extension 해서 만들어 보자

 

extension MutableCart on Cart {
  //카드 갱신
  Cart addItem({required ProductID productId, required int quantity}) {
    final copy = Map<ProductID, int>.from(items);
    
    //기존의 것을 복사해서 새로 cart를 update 함
    copy[productId] = quantity + (copy[productId] ?? 0);
    return Cart(copy);
  }
  
  //카드에서 삭제
  Cart removeItemById(ProductID productId) {
    final copy = Map<ProductID, int>.from(items);
    copy.remove(productId);
    return Cart(copy);
  }
}

이렇게 하면 카트 안의 값들은 변경되지만 최종적으로 immutable한 새로운 카트 객체를 얻게 된다.

 

  →   많은 상태관리가 immutable한 객체에 의존하고 있기 때문에,

         모델의 상태를 변경하기 위해서는 새로운 immutable한 객체를 복사한 뒤, 그 안에서 변경해야 한다. 

 

 

 

★ 핵심

모델/엔티티에 대해서 immutable data class로 만드는 것이 중요하다 

 

 

모델의 단위테스트에 대해서는 나중에 따로

테스트 part에 모아서 정리한 뒤 올리도록 하겠당..

728x90
반응형
320x100
728x90

 

 

riverpod를 상태관리에 대해 자세히 알아보던 도중,

잘 정리되어 있는 글을 발견했다. 

해당 글을 정리 및 번역한 글이며 원본 글은 아래에서 확인할 수 있다.

 

 

https://codewithandrea.com/articles/flutter-repository-pattern/

 

Flutter App Architecture: The Repository Pattern

An in-depth overview of the repository pattern in Flutter: what it is, when to use it, and various implementation strategies along with their tradeoffs.

codewithandrea.com

 

 

 

 


 

 

 

 

 

Riverpod 상태관리를 활용한 아키텍처에 대해 설명해보려 한다.

 

Riverpod 아키텍처는 4개의 레이어로 구성된다. 

(Data, Domain, Application, Presentation)

 

 

위의 각 화살표들은 레이어들 간의 종속성을 의미한다. 

하나씩 자세히 알아보도록 하자. 

 

 

 

1. 데이터 계층(Data Layer) 

  • Data Sources에 있는 세부 정보들로부터 도메인 모델(엔티티)를 분리하는 역할을 한다.
  • 데이터 전송 객체를 도메인 계층에서 이해할 수 있는 데이터로 변환시켜 준다.
  • 데이터 캐싱 역할 또한 한다.

 

 

 - Repository Pattern

     데이터 계층에서 repository pattern을 사용하여 Backend의 API와 같은 다양한 데이터 개체에 접근할 수 있으며, 

     앱의 도메인 계층에서 엔티티들이 안전한 유형으로 존재할 수 있도록 한다.

 

    즉, 데이터 로직비즈니스 로직분리하여 도메인에서 일관된 인터페이스를 통해

          데이터를 요청할 수 있도록 해주는 패턴이다.

 

 

 

 

★ 언제 이 패턴을 사용하나?

     비정형 데이터(주로 Json 데이터)를 반환할 때 주로 사용..

  1.  REST API를 사용할 때,
  2.  local 혹은 remote DB 저장소를 사용할 때(Hive, Firestore, etc..)
  3.  기기별 특정 API 호출할 때(permissions, camera, location, etc..)

 

 

 

 

 

예를 들어서 이해해 보자. 

아래의 api에서 날씨 정보를 flutter 앱에 받아오려고 한다. 

https://openweathermap.org/api

 

Weather API - OpenWeatherMap

Please, sign up to use our fast and easy-to-work weather APIs. As a start to use OpenWeather products, we recommend our One Call API 3.0. For more functionality, please consider our products, which are included in professional collections.

openweathermap.org

 

이를 받아오는 repository 인터페이스를 만들면 다음과 같다.

 

abstract class WeatherRepository {
	Future<Weather> getWeather({required String city)};
}

 

http 또는 dio를 통해 api를 호출할 수 있다.

다음은 http를 통해 weatherrepository를 구체화한 httpWeatherRepository 클래스 이다. 

 

import 'package:http/http.dart' as http;

class HttpWeatherRepository implements WeatherRepository {
  HttpWeatherRepository({required this.api, required this.client});
  
  final OpenWeatherMapAPI api;

  final http.Client client;

  Future<Weather> getWeather({required String city}) {
    	...
  }
}

    이렇게 따로 파일을 두어 클래스를 생성하면, 앱의 다른 부분은 이에 대해 신경쓰지 않고 각자의 일을 수행할 수 있다.

 

 

 

 

 

  - JSON data Parsing하기

    api를 통해 불러온 데이터는 Weather Model 클래스에 정의되어야 한다. 

 

   예전에 자동으로 model 생성해주는 quicktype.io 를 소개했지만, 이번에는 

    freezed 패키지를 통해 만들어 보았다. 

 

     Freezed에 대한 소개는 다음 글을 참고하면 된다..

    https://yerim-coding.tistory.com/26

 

[Flutter/플러터] Freezed 플러그인 사용하기 - (1)

Freezed란? 데이터 클래스에 필요한 편의 기능들을 Code Generation으로 제공하는 라이브러리 아래 공식문서를 살펴보자 freezed | Dart Package Code generation for immutable classes that has a simple syntax..

yerim-coding.tistory.com

 

@Freezed
class Weather with _$Weather{
   const factory({
   		required String city,
        ...
  factory Weather.fromJson(Map<String, dynamic> json) {
      ...
    }
}

 

생성한 model과 repository를 앱에서 초기화 하는 방법이다.

나는 riverpod를 사용할것 이기 때문에 

 

import 'package:flutter_riverpod/flutter_riverpod.dart';

final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
  return HttpWeatherRepository(api: OpenWeatherMapAPI(), client: http.Client());
});

다음과 같이 초기화 해 주면 된다. 

 

 

 

 

 

- 추상클래스(abstract class)구체 클래스(concrete class)언제 사용하면 될까?

 

각각의 장단점을 알아보자

 

추상 클래스를 사용할 때

장점 :  복잡하지 않다. 인터페이스를 한 곳에 볼 수 있어서 좋다.

            함수명을 바꾸고 싶을 때, 초기화 코드에서 한줄만 바꾸면 된다.

단점 : 상용구 코드가 길어질 수 있다

 

구체적인 클래스 사용할 때

장점 : 상용구 코드가 적다

단점 : repository 명을 바꾸려면 많은 곳에서의 수정이 필요하다

 

 

 


 

 

 

 

repository 패턴을 사용하면 데이터 계층의 구현을 따로 관리할 수 있다.

따라서, 도메인과 프레젠테이션 계층은 각자의 역할에만 충실할 수 있으며

모델 클래스와 엔티티들이 안전하게 타입을 유지할 수 있다. 

 

따라서 각 계층들을 분리하여 코드를 작성하는 것이 중요하다고 할 수 있다.

 

 

 

 

 

 

 

 

 

Repository 테스트 방법은 건너 뛰었다.. 알고싶으면 상단의 링크로 들어가보자

source code 또한 제공해 준다.

예제를 보면서 더 익혀 봐야 겠다..

https://github.com/bizz84/open_weather_example_flutter

728x90
반응형
320x100
728x90

 

Freezed란?

데이터 클래스에 필요한 편의 기능들을 Code Generation으로 제공하는 라이브러리

 

 

아래 공식문서를 살펴보자

 

 

 

 

freezed | Dart Package

Code generation for immutable classes that has a simple syntax/API without compromising on the features.

pub.dev

 

 

 


 

 

 

사용 방법)

1.  pubspec.yaml 파일에 build_runner와 Freezed를 설치한다.

   -  터미널에서 설치

flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev freezed


# 만약 fromJson 혹은 toJson으로 변환하는 기능을 사용하려면:
flutter pub add json_annotation
flutter pub add --dev json_serializable

 

 

 - 바로 설치

dependencies:
  freezed_annotation:

dev_dependencies:
  build_runner:
  freezed:
  json_serializable:

 

 

 

2. dart 파일에서 freezed 패키지 import

import 'package:freezed_annotation/freezed_annotation.dart';

 

 

 

 

3. freezed annotation과 만들고자 하는 클래스를 만든다

    아래는 공식 문서에 나와있는 예시 클래스 이다. 

//#1. freezed annotion 하기 

@freezed
class Person with _$Person {            //#2. 클래스 만들기
	const factory Person({
        required String firstName,     
        required String lastName,
        int? age,                       
     }) = _Person;
     
    //#3. fromJson 기능을 추가하고 싶을 때,
    factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
    
}

 

 

Person 클래스 안에서 factory 생성자에 필요한 property를 작성한다

 

     항상 값을 받아오면 required를 꼭 붙여주어야 하며, 

     만약 null 값을 허용하는 변수라면 변수 형 옆에 ? 를 붙여주면 된다. 

 

 

 

 

아래 모델 형태를 보며 이해하면 더 쉽다

class Product {
    Product({
        required this.fisrtName,
        required this.lastName,
        required this.age,
    });

    String fisrtName;
    String lastName;
    int? age;
}

 

 

 

 

4. 아래 코드를 추가한 뒤, build_runner를 실행한다. 

part 'person.freezed.dart';
part 'person.g.dart';

json_serializabled 의 fromJson/toJson 기능을 사용하고 싶으면 두번째  part 'person.g.dart';  를 추가하거나

그렇지 않으면 위 코드만 넣으면 된다.

 

 

 

▶ build_runner를 통해 code generation 실행하기 

flutter pub run build_runner build

 

 

터미널에서 해당 코드를 실행하고 나면 다음과 같이 두개의 파일이 추가로 생성되는 것을 볼 수 있다.

(필자는 userLogin이라는 클래스를 만들었다..)

 

 

 

 

최종 코드 

import 'package:freezed_annotation/freezed_annotation.dart';


part 'person.freezed.dart';
part 'person.g.dart';


@freezed
class Person with _$Person {            
	const factory Person({
        required String firstName,     
        required String lastName,
        int? age,                       
     }) = _Person;
     
    factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
    
}

 

 

추가 기능들에 대해서는 써보면서 다시 추가를 해 보겠다..

728x90
반응형
320x100
728x90

 

1. obs 변수를 만들어준다

var like = false.obs;

 

 

2. Obx 로 감싼 뒤, 위젯을 리턴해준다

Obx(() => CircleAvatar(
                  backgroundColor: Colors.white,
                  radius: 15,
                  child: Center(
                    child: IconButton(
                      ...
                    ),
                  ),
                ),
              ),

 

 

3. 삼항 연산자를 사용해 조건에 알맞은 아이콘을 보여주도록 한다.

product.like.value ? Icon(Icons.favorite_rounded) : Icon(Icons.favorite_border),

 

 

 

4. 눌렀을 때, 이벤트 처리

onPressed: () {
	product.like.toggle();
  },

 


 

Obx(() => CircleAvatar(
                  backgroundColor: Colors.white,
                  radius: 15,
                  child: Center(
                    child: IconButton(
                      alignment:Alignment.center,
                      icon: product.like.value ? Icon(Icons.favorite_rounded) : Icon(Icons.favorite_border),
                      onPressed: () {
                        product.like.toggle();
                      },
                      iconSize: 18,
                    ),
                  ),
                ),
              ),
728x90
반응형
320x100
728x90

 

 

반복문을 사용하지 않고 리스트의 총 합을 구할 수 있는 방법이 있다.

fold ( )를 사용하면 된다. 

 

 

 

공식 문서를 보자

https://api.dart.dev/stable/1.10.1/dart-core/List/fold.html

 

fold method - List class - dart:core library - Dart API

dynamic fold( initialValue,dynamic combine(previousValue, E element) ) Reduces a collection to a single value by iteratively combining each element of the collection with an existing value Uses initialValue as the initial value, then iterates through the e

api.dart.dev

 

 

 

dynamic fold(

    initialValue,
    dynamic combine(previousValue, E element)

)

 

 

collection의 각 요소를 기존 값과 반복적으로 결합하여 collection의 합을 단일값으로 만들어준다. 

initialValue를 초기 값으로 사용한 다음,

요소를 반복하고 결합 함수를 사용하여 각 요소로 값을 업데이트 시킨다. 

 

 

var value = initialValue;
for (E element in this) {
  value = combine(value, element);
}
return value;

 

 

사용은 다음과 같다. 

iterable.fold(0, (prev, element) => prev + element);

 

 

예시)

List<int> myList = [1, 3, 5, 8, 7, 2, 11];
int result = myList.fold(0, (sum, element) => sum + element);

print(result);
output : 37

 

 

 

리스트 안에서 가장 큰 수 구하기

 

final myList = [1, 3, 5, 8, 7, 2, 11];
final int result = myList.fold(myList.first, (max, element) {
    if (max < element) max = element;
    return max;
  });

print(result);
output : 11

 

 

가장 작은 수 구하기

final myList = [10, 3, 5, 8, 7, 2, 11];
final int result = myList.fold(myList.first, (min, element) {
    if (min > element) min = element;
    return min;
  });

  print(result);
output : 2

 

 

참고 및 출처 : https://www.kindacode.com/article/flutter-dart-fold-method-examples/

728x90
반응형