Skip to main content

27 posts tagged with "flutter"

View All Tags

· One min read
Park Ki Hyun

skipLoadingOnRefresh


when 메서드에서 skipLoadingOnRefresh라는 메서드가 있는데 true가 디폴트다. 새로고침해도 loading 창 안보여주는건데 false로 해주는 편이 좋다.

새로고침


ref.refresh() 또는 ref.invalidate()를 사용한다. 예시로는 RefreshIndicator로 했지만 버튼으로 하는게 좋은 UI라고 하는 듯

그리고 윈도우에서는 RefreshIndicator 동작안한다..

freezed class override



class User with _$User {
const User._();

...

String toString() => 'User(id:$id)';
}

freezed data class 에서 User._()를 선언하고 toString을 다시 오버라이딩 할 수 있던데 이에 대해서 조금 더 알아봐야 할 듯

· 3 min read
Park Ki Hyun

가상 API


여기 에서 다양한 API를 제공해준다.

users API로 FutureProvider 실습을 한다.

autoDispose 사용 여부


만약 특정 페이지 진입 시 API를 통해서 값을 가져왔을 때 이 값을 캐싱 할 것이면 autoDispose 사용하면 안되고 다시 안 가져올 것이면 autoDispose 사용하는 것이 좋다.

설정값 같은 경우는 API를 쏴서 변경하는 경우가 거의 없으므로 캐싱 하는 것이 좋다.

future provider body


when

값을 불러올 때 when을 사용한 모습입니다.

body: userList.when(
data: (users) {
return ListView.separated(
itemCount: users.length,
separatorBuilder: (context, index) {
return const Divider();
},
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: CircleAvatar(
child: Text(user.id.toString()),
),
title: Text(user.name),
);
},
);
},
error: (e, st) {
return Text(
e.toString(),
style: const TextStyle(fontSize: 20, color: Colors.red),
);
},
loading: () => const Center(
child: CircularProgressIndicator(),
),
),

switch expression

값을 불러올 때 switch를 사용한 모습입니다.

body: switch (userList) {
AsyncData(value: final users) => ListView.separated(
itemCount: users.length,
separatorBuilder: (context, index) {
return const Divider();
},
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: CircleAvatar(
child: Text(user.id.toString()),
),
title: Text(user.name),
);
},
),
AsyncError(error: final e) => Text(
e.toString(),
style: const TextStyle(fontSize: 20, color: Colors.red),
),
_ => const Center(
child: CircularProgressIndicator(),
),
},

마지막에 원래 _ 대신 AsyncLoading이 들어가야 하지만 무슨 에러가 난다.

userListProvider를 @Sealed로 만들었어야 한다고 한다.

keepalive


하면서 궁금증이 생겼었는데 값을 가져왔으면 다시 나갔다 왔을 때 loading 안 보고 싶었습니다.

그러면 autoDispose일 때 ref.keepAlive()를 사용하면 됩니다.
주의할 점은 http 호출이 끝나고 적용해야 합니다.

값을 가져온 것을 성공한 페이지는 값이 유지가 되고(dispose안함) error가 나면 dispose 합니다.

· 2 min read
Park Ki Hyun

state provider


특징으로는 code generator가 안되고

state를 갖고 있다는 점

floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(counterProvider.notifier).state++;
},
child: const Icon(Icons.add),
),

위와 같이 read를 하는데 notifier를 사용해서 state에 접근할 수 있다.

그리고 추가로 UI를 그리려면 Future.delay 또는 Listen을 사용할 수 있다.
(그냥 그리려면 에러난다. 충돌나서)

ref.listen<int>(counterProvider, (previous, next) {
if (next == 3) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('counter : $next'),
);
},
);
}
});

요런식으로 listen할 수 있다. 이전 값 다음 값이 있다.

update


onPressed: () {
ref
.read(autoDisposecounterProvider.notifier)
.update((state) => state + 10);
},

위 코드는

ref.read(autoDisposecounterProvider.notifier).state + 
ref.read(autoDisposecounterProvider.notifier).state + 10

과 같은 코드이다. 너무 길기 때문에 그냥 update 사용하는 것이 편리하다.

state를 인자로 사용할 로직이 필요하면 update 사용하기

Future Provider의 이야기


remote api를 호출하면 3가지 상태 처리가 필요하다.

in progress, success, error이다.

3가지 모두 동시에 2가지 이상 상태가 될 수 없다.

FutureProvider는 이 3가지 상태에 대한 값을 처리해준다.

AsyncData, AsyncError, AsyncLoading이 있다.

생성자도 3가지 있다.

AsyncValue가 중요하다는 뜻

소소한 팁


  1. provider 안에 ref.read()를 사용하지 말 것

  2. StateProvider 대신 NotifierProvider 사용할 것

· 3 min read
Park Ki Hyun

final helloProvider = Provider<String>((ref) {
ref.onDispose(() {
print("[helloProvider] : disposed");
});
return "Hello";
});

riverpod의 기본 모습이 이렇다.

변수에 할당하고 Provider 부분은 사용할 Provider 종류,

<String>에는 return값 타입을 정의한다.

ref를 통해서 여러 기능을 사용할 수 있는데 ref.onDispose해주면 dispose된다.

onDispose

해당 provider가 dispose되면 실행되는 함수

위의 코드는 자동으로 dispose 되지 않는다.

대표적 3가지

watch : 값 변하는지 계속 확인하고 변하면 리빌드 (async하면안된다.)

listen : 값 변경되면 리빌딩 되는 것이 아닌 액션을 수행(navigate같은) (async하면 안된다.)

read : 값을 확인하지만 변경을 watch하지 않음(build에서 사용하지마라)

auto dispose


final autoDisposeHelloProvider = Provider.autoDispose<String>((ref) {
print('[autoDisposeHelloProvder]: created');
ref.onDispose(() {
print('[autoDisposeHelloProvder]: disposed');
});
return 'Hello';
});

페이지에서 나오면 autodispose해준다.

family


final familyHelloProvider = Provider.family<String, String>((ref, name) {
ref.onDispose(() {
print('[familyHelloProvider] disposed');
});
return 'Hello $name';
});

이렇게 provider 정의하면 argument 사용할 수 있다.

    final helloA = ref.watch(familyHelloProvider('A'));
final helloB = ref.watch(familyHelloProvider('B'));

watch할 때도 이런식으로 해야한다.

autoDisposeFamily


final autoDisposeFamilyHelloProvider =
Provider.autoDispose.family<String, String>((ref, name) {
ref.onDispose(() {
print('[autoDisposeFamilyHelloProvider] disposed');
});
return 'Hello $name';
});

autoDispose와 family를 모두 사용할 수 있다.

만약 둘의 인자값이 A라면 dispose는 한번만 일어납니다. (그냥 autoDispose도 한번만 일어남)

객체 넘기기


class Counter {
final int count;
Counter({
required this.count,
});


String toString() => 'Counter()(count: $count)';
}

final counterProvider = Provider.autoDispose.family<int, Counter>((ref, c) {
ref.onDispose(() {
print('[countProvider($c)] disposed');
});
return c.count;
});

만약 위처럼 객체를 넘겨서

    ref.watch(counterProvider(Counter(count: 0)));
ref.watch(counterProvider(Counter(count: 0)));

이렇게 watch를 조지면 dispose가 두번 일어납니다. 둘 다 다른 객체로 판단합니다.

class Counter extends Equatable {
final int count;
const Counter({
required this.count,
});


String toString() => 'Counter()(count: $count)';


List<Object> get props => [count];
}

Counter 객체를 Equatable해야 dispose 한번만 일어난다.

정리


autoDispose 조심히 사용해야 한다. 계속 dispose 하기 때문에

· 3 min read
Park Ki Hyun

code generator


rivp: riverpodPart

part 'b_provider.g.dart';

riverpodKeepAlive

riverpod code generator는 자동으로 autoDispose인데 keepAlive:true를 해주면 autoDispose하지 않는다.

(keepAlive: true)
String hello(HelloRef ref) {
ref.onDispose(() {
print("[helloProvider] : disposed");
});
return "Hello";
}

String: return의 타입 hello: 나중에 helloProvider라는 이름을 갖는다. HelloRef: generator하면 Ref로 나오는데 hello에서 앞글자를 대문자로 해서 붙이면 HelloRef가 된다. 이걸로 교체해줘야한다.

코드생성

dart run build_runner build -d

위 처럼 build runner 실행해서 generate할 수 있다.

-d옵션은 generate중 충돌이 나도 그냥 생성하라는 뜻입니다.

dart run build_runner watch -d

watch를 사용하면 한번만 사용하면 변화를 계속 watch해서 적용된다.

차이점

수동 생성과 차이점은

  1. 기존은 helloProvider 이렇게 했다면 자동 생성에서는 hello로만 해도 helloProvider 생성해줌.

  2. 자동생성에서는 Provider 종류 지정 안해도된다.

Provider


autoDispose


autoDispose Provider는 riverpod스니팻 으로 만들 수 있습니다.


String autoDisposeHello(AutoDisposeHelloRef ref) {
print('[autoDisposeHelloProvder]: created');
ref.onDispose(() {
print('[autoDisposeHelloProvder]: disposed');
});
return 'Hello';
}

family


family는 그냥 riverpod이나 riverpodkeepalive에서 ref 옆에 파라미터 추가해주면 family로 자동으로 만들어줍니다.

(keepAlive: true)
String familyHello(FamilyHelloRef ref, {required String name}) {
ref.onDispose(() {
print('[familyHelloProvider] disposed');
});
return 'Hello $name';
}

참고로 name을 사용하면 에러난다. 그 이유는 generator에서 name이라는 변수를 이미 사용하고 있기 때문이다.

autoDispose, family



String autoDisposeFamilyHello(AutoDisposeFamilyHelloRef ref,
{required String nom}) {
ref.onDispose(() {
print('[autoDisposeFamilyHelloProvider] disposed');
});
return 'Hello $nom';
}

별로 어렵지 않음

한가지 배운 dart 언어 syntax


partimport다음으로 최상단에 위치해야한다.

StateProvider


특징

  • stateProvider는 notifierProvider의 간소화된 버전
  • 복잡하지 않는 로직을 쓸 때 사용
  • code generator해서 만들 수 없다.(수동만 가능)

· 3 min read
Park Ki Hyun

factory Constructor


factory constructor의 constructor redirection에 대해 알고 있어야 한다. 그리고 const를 사용할 수 있음.

필요한 패키지


freezed_annotation: dep : 코드 생성기에 대한 어노테이션 패키지

build_runner : dev-dep : 코드 생성기 실행

freezed : dev-dep : 코드 생성기

json_annotation : dep : 코드 생성기인 json_serializable의 어노테이션 갖고 있는 패키지

json_serializable : dev-dep : 코드 생성기

freezed와 json_serializable 사용할 경우

analysis_options.yaml에 설정

analyzer:
errors:
invalid_annotation_target: ignore
exclude:
- '**/*.freezed.dart'
- '**/*.g.dart'

build runner를 사용해서 코드 gen하려면

dart run build_runner build [--delete-conflicting-outputs]

or

dart run build_runner watch [-d]

build는 one-time, watch는 계속 빌드함

watch가 빌드 타임 적다.

[]에 있는 옵션은 previous build에서 발생한 것 건너뛰기

extension

freezed라는 확장 설치해야 한다.

ptf : part '.freezed.dart'; 생성 pts : part '.g.dart'; 생성 fdataclass : Dataclass 생성 funion : Union class 생성

기능


정리

우선 다양한 것들을 override해준다.

toString이나 copyWith, equality 등을 오버라이딩 해줌.

copyWith

일반적인 copyWith + null도 지원해줌

deep copyWith도 제공해준다. 복잡한 코드를 간단하게 작성할 수 있도록 지원

Json annotation

(name: 'parking_lot_capacity') int? parkingLotcapacity,

data class를 만들 때 이런 인자값을 볼 수 있다.

변수 parkingLotcapacity를 json키 값 'parking_lot_capacity'로 판단하겠다는 뜻이다.

  factory Hotel.fromJson(Map<String, dynamic> json) => _$HotelFromJson(json);

이런 것도 제공해준다.

Provider


위젯이나 다른 Provider에 값을 제공

riverpod에서는 Provider를 watch할 수 있다. 그리고 그냥 값을 제공하는 Provider다.

dep

equatable,flutter_riverpod,riverpod_annotation

dev-dep

build_runner,custom_lint,flutter_lints,riverpod_generator, riverpod_lint

· 2 min read
Park Ki Hyun

Equatable


dart class generator라는 vs code extension을 사용할 때 객체 비교를 위해서 equatable이라는 dart 패키지를 이용할 수 있습니다.

vs code preferences에서 equatable 관련 설정을 true로 변경한다면 generator를 이용할 수 있습니다.

함수를 통해 객체를 생성하면 주소 값이 달라져서 같은 값을 갖고 있는 객체를 비교해도 false로 나오는데 equatable을 이용해서 비교하면 true를 반환할 수 있습니다.

Json 관련


json object로 자동 data class가 생성되는 마법이 있다고 합니다.

  1. 일단 dart 파일을 만듭니다.

  2. api 응답 json 값을 넣습니다.

  3. ctrl+shift+p해서 Generate from JSON 입력해서 선택

  4. class 이름 입력

  5. no 선택

그럼 data class 자동 생성 해준다.

fromMap, toMap만 잘 확인해보면 된다. (beta 단계임)

· One min read
Park Ki Hyun

Treeview


이 라이브러리를 사용한다.

animated_tree_view다.

TreeNode.root..addAll([]) 해서 배열을 넣을 수 있다.

  final myTree = TreeNode.root()
..addAll([
TreeNode(key: "Today"),
TreeNode(key: "Tomorrow")
..addAll([
TreeNode(key: "task 1"),
TreeNode(key: "task 2"),
TreeNode(key: "task 3"),
]),
TreeNode(key: "Upcoming"),
]);

TreeView.simple을 사용해봤는데 builder에서 인자로 context, node를 받는다.

주의할 점은 key로 하기 때문에 key 중복되지 않게 조심해야한다. 같은 level만 아니면 괜찮다. (같은 level아니면 이름 중복 가능)

· One min read
Park Ki Hyun

Future.wait


  Future<void> searchProductList() async {
isBusy = true;
final results = await Future.wait([
productRepository.searchProductList(keyword),
Future.delayed(const Duration(milliseconds: 555)),
]);
productList = results[0];
isBusy = false;
}

Future.wait이라는게 있는데 안에 있는 Future wait의 배열 명령어들이 모두 끝날 때까지 기다림

즉 위 코드 상으로는 search하는거와 delayed를 동시에 돌리는데 555 millisecond는 무조건 돌고 그 이상은 search끝날 때까지 기다린다는 뜻

· 3 min read
Park Ki Hyun

ClipRRect


container를 border radius하게 디자인 했는데 스크롤 바가 오버플로우 되는 현상이 있었다.

ClipRRect로 스크롤바를 만드는 listview 위젯을 감싸니 오버플로우 되는 부분은 안보이게 됐다.

ClipRRect위젯은 클리핑하는 위젯인데 클리핑이란 주어진 경계 내에서만 내용을 보여주고 경계 넘어서는 잘라내는 것을 의미한다고 한다.

주로 이미지를 둥근 액자에 넣고 싶을 때도 사용한다고 한다..

listView


사실 바보같은 짓을 했다 listView 안에 expanded를 넣으려고 했다. 왜냐하면 width를 double.infinity하게 하고싶었었다. 그러나 listView를 잘 몰랐었다.

listView의 자식들은 listView의 크기를 따라간다. 그래서 listView를 Expanded안에 infinity로 두면 그 안에도 infinity다. 그렇게 해결했다.

GestureDetector의 behavior


tip

deferToChild: 자식 중 하나가 적중 시 이벤트 수신(투명한 대상 이벤트 수신 x)
translucent : 반투명한 대상 & 시각적으로 뒤에 있는 대상도 이벤트 수신
opaque : 시각적으로 뒤에 있는 대상은 이벤트 수신 불가능

behavior: HitTestBehavior.translucent,

이와 같이 사용한다면 패딩부분도 클릭한다면 이벤트 수신될 수 있다.

Divider


ListView.separated를 사용하여 separatorBuilder를 통해 구분자를 넣을 수 있었다.

줄을 긋고 싶었는데 Divider를 사용할 수 있었다.

height로 상하 여백을 정할 수 있었고
thickness로 줄의 굵기를
indent,endIndent로 좌우 여백을
color로 색상을 정할 수 있었다.

리랜더링


다국어 지원 설정을 했는데 값은 변경 됐는데 화면 리랜더링을 못해서 계속 애먹었었다. 결국에는 riverpod provider를 수정하여 다국어도 watch하는 방법으로 변경했는데 현업에서 이렇게 사용하는지는 아직 모르겠다..