Skip to main content

26 posts tagged with "TIL"

View All Tags

· 2 min read
Park Ki Hyun

ContextMenu


GestureDetector를 사용하여 우클릭 이벤트 시 컨텍스트 메뉴가 나오게 할 수 있다.

우클릭 이벤트는 onSecondaryTapDown 또는 onSecondaryTapUp인데 Up이 좋은게 다운 했다가 다른 곳에서 Up하면 취소되는거도 자연스럽게 적용된다.

먼저 overlay를 구해야 한다.

final RenderObject? renderObject =
Overlay.of(context).context.findRenderObject();
if (renderObject is RenderBox) {
final RenderBox overlay = renderObject;
...

렌더링할 object를 위젯 트리 상에서 가장 가까운 위젯을 build context를 통해 찾는다고 하네요

showMenu(
context: context,
position: RelativeRect.fromRect(
details.globalPosition & const Size(48, 48),
Offset.zero & overlay.size,
),
items: [
const PopupMenuItem(
value: 'menu1',
child: Text('메뉴 1'),
),
const PopupMenuItem(
value: 'menu2',
child: Text('메뉴 2'),
),
],
).then((value) {
// 메뉴 선택에 따른 동작
if (value != null) {
print('선택된 메뉴: $value');
}
});

showMenu 함수를 사용해서 context 메뉴를 정의할 수 있는데

postion 속성의 값으로 첫 번째 인자로 Rect rect를 받고 두 번째 인자로는 Rect container를 받습니다.

정확하게는 모르겠는데 Size(,)의 값을 수정하면 context menu가 제한된 곳을 얼만큼 나갈 수 있는지에 대한 값인 것 같고

item을 통해서 메뉴 값을 정할 수 있고

then을 통해서 눌린 버튼에 대한 이벤트를 정의할 수 있습니다. 눌린 버튼은 item 리스트에 정의된 value값을 then에 넘기기 때문에 switch case로 구분 가능

· 7 min read
Park Ki Hyun


플러터에서 창 전환하는 방법은 여러 가지가 있다.

나는 Navigator.pushNamed가 좋다.

방법은 간단하다. onGenerateRoute 함수를 만들어서 어떤 이름이 들어왔을 때 어떤 페이지로 라우팅 해줄지 정하면 된다.

  static const String main = 'main';
static const String login = 'login';

static Route<dynamic>? onGenerateRoute(RouteSettings settings) {
late final Widget page;
switch (settings.name) {
case RoutePath.login:
page = const LoginPage();
break;
case RoutePath.main:
page = const MainPage();
break;
}

return MaterialPageRoute(builder: (context) => page);
}

간단하게 이런식이다. 페이지 이름들을 정의하고 switch case를 사용하여 => page 이런식으로 라우팅을 한다.

main.dart에서 MaterialApp안에 아래와 같이 작성만 하면된다.

      // 초기 페이지 설정
initialRoute: RoutePath.login,
// onGenerateRoute 설정
onGenerateRoute: RoutePath.onGenerateRoute,

페이드 아웃 방식

return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
transitionDuration: const Duration(seconds: 1),
);

페이드 아웃을 하려면 FadeTransition을 사용할 수 있다.

그리고 transitionDuration 속성을 사용해서 페이드 아웃 시간도 정할 수 있다.

슬라이드 방식

  return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(10.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;
final tween =
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
transitionDuration: const Duration(milliseconds: 500),
);

이건 슬라이드 방식이다.

begin, end에서 사용하는 Offset은 좌표를 나타낸다. bigin의 Offset x좌표 크기가 작으면 움직이는 속도도 작아짐

curve 관련해서는 아래 정리한다.

curve

Curves.linear: 일정한 속도로 애니메이션을 진행합니다. Curves.ease: 처음과 끝은 천천히, 중간은 빠르게 가속하는 가속도 곡선입니다. Curves.easeIn: 처음에 천천히 가속하는 가속도 곡선입니다. Curves.easeOut: 끝에서 천천히 감속하는 감속도 곡선입니다. Curves.easeInOut: 처음과 끝에서 천천히 가속하고 중간에서 천천히 감속하는 곡선입니다. Curves.bounceIn: 바운스 효과가 있는 곡선으로, 처음에 튀어오르는 효과가 있습니다. Curves.bounceOut: 바운스 효과가 있는 곡선으로, 끝에서 튀어오르는 효과가 있습니다. Curves.elasticIn: 탄력 효과가 있는 곡선으로, 처음에 잠깐 튕기는 효과가 있습니다. Curves.elasticOut: 탄력 효과가 있는 곡선으로, 끝에서 잠깐 튕기는 효과가 있습니다.

그렇게 해서 SlideTransition를 사용해서 슬라이드 형식으로 한다.

transitionsBuilderchild값에는 pageBuilder에서 return한 page가 들어간다.

애니메이션


  void _animateWindowAndNavigate(BuildContext context) {
const targetSize = Size(800, 800);
const duration = Duration(milliseconds: 500); // 애니메이션 지속 시간
const stepDuration = Duration(milliseconds: 10);
int steps = duration.inMilliseconds ~/ stepDuration.inMilliseconds;
double widthStep = (targetSize.width - appWindow.size.width) / steps;
double heightStep = (targetSize.height - appWindow.size.height) / steps;

Timer.periodic(stepDuration, (timer) {
appWindow.size = Size(
appWindow.size.width + widthStep,
appWindow.size.height + heightStep,
);

if (appWindow.size.width >= targetSize.width ||
appWindow.size.height >= targetSize.height) {
timer.cancel();
appWindow.size = targetSize;

// 창 크기 조정 후 페이지 전환
Navigator.pushNamed(context, RoutePath.main);
}
});
}

페이지가 변할 때 각 페이지에 맞는 크기를 위해서 처음에 페이지에 도달하면 그냥 바로 변하게 했더니 버벅거리는 것 같아서 사이즈를 키우고 화면 전환하도록 해봤다.

기존 크기에서 스텝마다 계산된 값(widthStep,heightStep)을 스텝의 시간(stepDuration)마다 키우는 것이다.

    Timer.periodic(stepDuration, (timer) {
appWindow.size = Size(
appWindow.size.width + widthStep,
appWindow.size.height + heightStep,
);

...

})

Timer.periodic을 사용해서 stepDuration 마다 아래 로직을 수행하면서 if문을 통해 조건이 충족한다면 timer.cancel 즉 무한루프를 나오는 듯한 break를 거는 로직ㅇ비니다.

근데 위는 화면이 커질수록 버벅거린다. 그래서 마지막에 커지는 속도를 줄여야 하나 했다. 아래와 같은 코드가 나왔다.

  void _animateWindowAndNavigate(BuildContext context) {
const targetSize = Size(800, 800);
const duration = Duration(milliseconds: 500); // 애니메이션 지속 시간
const timerDuration = Duration(milliseconds: 10);
const curve = Curves.decelerate;
int steps = duration.inMilliseconds ~/ timerDuration.inMilliseconds;
int currentStep = 0;

Timer.periodic(timerDuration, (timer) {
double fraction = (currentStep / steps);
// 'Curves.decelerate' 커브를 사용하여 변화율을 계산합니다.
double easedFraction = curve.transform(fraction);
double width = appWindow.size.width +
((targetSize.width - appWindow.size.width) * easedFraction);
double height = appWindow.size.height +
((targetSize.height - appWindow.size.height) * easedFraction);

appWindow.size = Size(width, height);

currentStep++;
if (fraction >= 1) {
timer.cancel();
appWindow.size = targetSize;

// 창 크기 조정 후 페이지 전환
Navigator.pushNamed(context, RoutePath.main);
}
});
}

위 코드는 마지막에 속도를 줄여야 하기에 Curves.decelerate를 사용해서 커브 값을 얻었고 변화율을 계산하여 적용해봤지만

그래도 버벅거림은 해결하지 못했습니다.

  static void animateWindowForMain(BuildContext context, String routePath) {
const targetSize = mainPageSize; // 정의된 main page size
const duration = Duration(milliseconds: 500); // 애니메이션 지속 시간
const curve = Curves.easeOutExpo; // 보다 부드러운 애니메이션을 위한 커브
final numFrames = duration.inMilliseconds ~/ 16; // 약 60fps

List<Size> frameSizes = List.generate(numFrames, (index) {
double progress = (index / numFrames);
double animatedValue = curve.transform(progress);
double width = appWindow.size.width +
((targetSize.width - appWindow.size.width) * animatedValue);
double height = appWindow.size.height +
((targetSize.height - appWindow.size.height) * animatedValue);
return Size(width, height);
});

int currentFrame = 0;
Timer.periodic(const Duration(milliseconds: 16), (timer) {
Size newSize = frameSizes[currentFrame];
appWindow.size = newSize;

currentFrame++;
if (currentFrame >= frameSizes.length) {
timer.cancel();
appWindow.size = targetSize;

// 창 크기 조정 후 페이지 전환
Navigator.pushNamed(context, routePath);
}
});
}

그래서 List<Size> 타입의 변수 안에 미리 크기들을 계산해서 넣어놓고 Timer.periodic에선 리스트에 있는 값을 꺼내기만 하면 되니 더욱 깔끔해질 수 있었습니다.

· 2 min read
Park Ki Hyun

다국어 설정


  1. flutter_localization,intl 패키지 추가
  flutter_localizations:
sdk: flutter
intl: ^0.18.1

꼭 위처럼 추가해야한다.

  1. vs code Flutter Intl extension 설치

  2. pubspec.yaml파일에 아래 내용 입력

flutter_intl:
enabled: true
arb_dir: lib/util/lang/l10n
output_dir: lib/util/lang/generated
  1. ctrl+shift+p로 명령어 팔레트 열고 flutter intl initialize

  2. 그러면 lib/util/lang 밑에 l10ngenerated가 생김

한국어 추가하기

  1. ctrl+shift+p로 열고 intl: Add locale하고 ko추가

  2. 그러면 intl_ko.arb파일 생긴다.

{
"@@locale" : "en",
"language" : "language"
}

{
"@@locale" : "ko",
"language" : "언어"
}

이런식으로 설정하면 된다.

추가 설정

  1. ios에서 사용하려면 ios/Runner/Info.plist 파일에서
<key>CFBundleDevelopmentRegion</key>
<array>
<string>en</string>
<string>ko</string>
</array>

key태그 밑에 array태그 추가하기

  1. main.dart 설정
import 'package:flutter_localizations/flutter_localizations.dart';

...
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,

/// 이거 넣어야 한다.
locale: ref.watch(langProvider),
...

그리고 추가적으로 구현해야할 것이 locale값을 watch하는 provider 만들어서 MaterialApp 안에 locale 속성값에 watch로 넣기

추가 팁



class Lang extends _$Lang {

Locale build() {
return LangState.en;
}

void toggleLanguage() {
state = LangState.isKo ? LangState.en : LangState.ko;
}
}

abstract class LangState {
static const Locale en = Locale('en');
static const Locale ko = Locale('ko');

static bool get isKo => Intl.getCurrentLocale() == ko.languageCode;
}

riverpod generater를 이용한 language provider 코드와 state 코드다.

· One min read
Park Ki Hyun

TextOverflow


Text위젯에서 maxLines를 정하고 overflow를 정의할 수 있다.

종류

  • TextOverflow.clip: 텍스트가 그 경계를 넘어가면 잘라냅니다. 이는 기본값입니다.

  • TextOverflow.fade: 텍스트가 그 경계를 넘어가면 점점 흐려지게 만듭니다.

  • TextOverflow.ellipsis: 텍스트가 그 경계를 넘어가면 끝에 '...'를 추가합니다.

  • TextOverflow.visible: 텍스트가 그 경계를 넘어가도 잘리거나 사라지지 않고 그대로 보이게 합니다.

fade나 ellipsis를 많이 쓸 듯

· 3 min read
Park Ki Hyun

typedef


typedef HelloThere = Pointer<Utf8> Function(Pointer<Utf8> str);

typedef를 사용하면 특정 타입을 커스텁 할 수 있다.

단 문제점이 하나 있다. 함수 안에서는 사용못한다. 무조건 함수 밖에서 사용..

dll 사용하기


dll 사용하려면 ffi라는 패키지를 설치해야한다. (dart:ffi랑은 다른 패키지다. 이게 있어야 Pointer<Utf8>사용 가능)

함수 불러오기

final DynamicLibrary dylib = DynamicLibrary.open('hello.dll');

위처럼 dll파일 읽어서 일단 dll파일 자체를 DynamicLibrary 타입의 변수에 저장하고 (이 때 hello.dll파일 위치는 root다.)

typedef HelloThereFunc = Pointer<Utf8> Function(Pointer<Utf8> str);
typedef HelloThere = Pointer<Utf8> Function(Pointer<Utf8> str);

편의를 위해서 2개의 함수형을 typedef로 정의합니다.

HelloThereFunc는 C 라이브러리의 native 함수이고 HelloThere는 Dart에서 사용하는 함수입니다.

타입이 Pointer<Utf8>인 이유는 Dart와 C의 문자열 나타내는 방법이 달라서 어쩔 수 없다고 하네요

final helloThere =
dylib.lookupFunction<HelloThereFunc, HelloThere>('HelloThere');

이제 실질적으로 dll에 있는 함수를 import하는 방법입니다.

dll파일에 DynamicLibrarylookupFunction<C_native_func, Dart_func>을 사용하여서 ('함수이름')이 함수를 호출하는 것입니다.

코드로 보자면

final 변수명 = dll이름.lookupFunction<C_네이티브함수_시그니처, Dart_함수_시그니처>(호출할 함수이름);

라고 정리할 수 있습니다.

사용법

final Pointer<Utf8> nName = 'Park'.toNativeUtf8();
final Pointer<Utf8> nRst = helloThere(nName);
calloc.free(nName);

Center(
child: Text(nRst.toDartString()),
),

간단한 사용 예시 입니다.

먼저 인자값 선언을 합니다(nName). 스트링을 toNativeUtf8()로 변환해주고

함수 호출하고

다 쓴 값은 free 시켜줍니다.

nRst도 free시켜줘야하는데 지금 시키면 프로그램이 꺼집니다. 이에 대해서는 조금 더 공부해봐야 합니다.

· 3 min read
Park Ki Hyun

Window app에서 상단바 커스텀


bitdoho_window라는 패키지를 이용한다.

초기값 설정

void main() {
runApp(const MyApp());
doWhenWindowReady(() {
final win = appWindow; // 정의
const initialSize = Size(600, 450); // 사이즈 정의
win.minSize = initialSize; // 최소 사이즈 할당
win.size = initialSize; // 초기 사이즈 할당
win.alignment = Alignment.center; // 실행시 화면 위치
win.title = "Custom Window app"; // 창 이름
win.show(); // 이거 있어야 보여준다.
});
}

테두리 설정


Scaffold(
// 테두리
body: WindowBorder(
color: const Color(0xFF0B4279),
width: 1,
child: const Row(
children: [
LeftSide(),
RightSide(),
],
),
),
),

WindowBorder라는 위젯을 사용해서 테두리를 설정할 수 있다. 테두리의 두께, 색상을 설정할 수 있다.

이동 설정

child: Column(
children: [
WindowTitleBarBox(
child: Row(
children: [
Expanded(
child: MoveWindow(),
),
const WindowButtons(),
],
),
),
],
),

WindowTitleBarBox를 정의하고 안에서 MoveWindow를 정의하면 해당 칸을 잡고 드래그하면 창도 드래그하도록 설정할 수 있다.

이벤트 버튼 설정

WindowButtonColors buttonColors = WindowButtonColors(
iconNormal: const Color(0xFF0B4279),
mouseOver: const Color(0xFF2A78C6),
mouseDown: const Color(0xFF0B4279),
iconMouseOver: const Color(0xFF0B4279),
iconMouseDown: const Color(0xFFCEE1FF),
);

Row(
children: [
MinimizeWindowButton(
colors: buttonColors,
),
MaximizeWindowButton(
colors: buttonColors,
),
CloseWindowButton(),
],
);

이미 정의 돼있는 이벤트 버튼을 사용할 수 있습니다. 여러 가지 색상 속성을 정의하여 커스텀할 수 있습니다.

플랫폼 별 설정


For Windows

#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);

위 내용을 windows\runner\main.cpp의 최상단에 작성해야 합니다.

For Mac

macos\runner\MainFlutterWindow.swift에서

import bitsdojo_window_macos // FlutterMacOS 밑에 추가

이 코드를 import FlutterMacOs밑에 추가하고

class MainFlutterWindow: NSWindow {

// 를 아래 코드로 변경

class MainFlutterWindow: BitsdojoWindow {
override func bitsdojo_window_configure() -> UInt {
return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP
}

이 코드를

override func awakeFromNib() {

이 코드 위에 추가합니다.

리눅스를 위한 방법도 있는데 따로 정리하진 않겠습니다.

· 3 min read
Park Ki Hyun

path_provider 종류


gpt의 답변을 정리합니다.

  1. getApplicationDocumentsDirectory()
  • Windows에서 이 함수는 사용자의 "Documents" 폴더에 있는 애플리케이션 데이터 디렉토리를 반환합니다. 보통 C:\Users\<User Name>\Documents 경로로 찾을 수 있습니다.
  1. getApplicationSupportDirectory()
  • C:\Users\User\AppData\Roaming\ [패키지이름]\ [프로젝트이름]
  1. getDownloadsDirectory()
  • 사용자의 "Downloads" 폴더를 반환합니다, 보통 C:\Users\<User Name>\Downloads에 위치합니다.
  1. getExternalCacheDirectories()
  • Windows에서 외부 캐시 디렉토리는 일반적으로 해당되지 않습니다. 이 함수는 Windows에서 지원되지 않을 가능성이 높습니다.
  1. getExternalStorageDirectories({StorageDirectory? type})
  • Windows에서 외부 저장소 디렉토리는 일반적으로 해당되지 않으며, 이 함수는 Android 플랫폼에 특화되어 있습니다.
  1. getExternalStorageDirectory()
  • 이 함수도 마찬가지로 Windows에서는 일반적으로 사용되지 않습니다. Android에서는 외부 저장소의 루트 디렉토리를 반환합니다.
  1. getLibraryDirectory()
  • macOS에서만 사용되며, Windows에서는 해당 디렉토리가 없습니다.
  1. getTemporaryDirectory()
  • 시스템의 임시 폴더를 반환합니다. Windows에서는 보통 C:\Users\<User Name>\AppData\Local\Temp에 위치합니다.

AsyncValeu - properties


value

Async종류previous value [X]previous value [O]
AsyncLoadingnullprevious value
AsyncDatacurrent valuecurrent value
AsyncErrorrethrow errorprevious value

error, stackTrace

Async종류previous error [X]previous error [O]
AsyncLoadingnullprevious error
AsyncDatanullnull
AsyncErrorcurrent errorcurrent error

isLoading/hasValue/hasError

Async종류previous value [X]
previous error [X]
previous value [X]
previous error [O]
previous value [O]
previous error [X]
previous value [O]
previous error [O]
AsyncLoadingisLoading [O]
hasValue [X]
hasError [X]
isLoading [O]
hasValue [O]
hasError [X]
isLoading [O]
hasValue [X]
hasError [O]
isLoading [O]
hasValue [O]
hasError [O]
AsyncDataisLoading [X]
hasValue [O]
hasError [X]
isLoading [X]
hasValue [O]
hasError [X]
isLoading [X]
hasValue [O]
hasError [X]
isLoading [X]
hasValue [O]
hasError [X]
AsyncErrorisLoading [X]
hasValue [X]
hasError [O]
isLoading [X]
hasValue [O]
hasError [O]
isLoading [X]
hasValue [X]
hasError [O]
isLoading [X]
hasValue [O]
hasError [O]

· 6 min read
Park Ki Hyun

AutoDispose


autodispose를 사용하려면

final counterProvider = NotifierProvider.autoDispose<Counter, int>(Counter.new);

로 선언해야 하고

class Counter extends AutoDisposeNotifier<int>{}

위처럼 Notifier를 사용해야 한다.

Family


final counterProvider = NotifierProvider.family<Counter, int, int>(Counter.new);

family를 사용하려면 family와 넘겨줄 인자값을 추가해야하고

class Counter extends FamilyNotifier<int, int> {

int build(int arg) {return arg;}
}

Notifier도 FamilyNotifier와 인자 추가와 build함수에 인자를 추가해야 한다.

AutoDispose Family


위의 둘을 합치면 된다.

riverpod generator 이용


generator를 이용해서 notifier를 만들면 더 쉽다.

  1. riverpodpart 만들기

  2. riverpodclass로 만들기 (riverpodclass가 notifier다.)

  3. 이름 넣고 build 함수 타입 지정하기

  4. 나머지 구현

AutoDispose는 어노테이션에 따라 결정되고 family 여부는 build함수 인자값 존재 유무로 결정된다고 합니다.

추가 특이사항이 있으면 그 때 정리해야지.

state shape


2가지 있다고 한다.

enum, sealed .. 그렇다

추가로 AsyncValue도 있다.

이곳의 API를 활용한다.

data model을 freezed로 만드는 절차


  1. ptf

  2. pts

  3. fdataclass

  4. factory 생성자에 인자값 넣기 required로 아니여도되구

  5. fromJson키워드로 fromJson 만들기 (이름은 class이름붙이고)

  6. 메서드들 만들기(empty,add,remove 등)

stat를 freezed로 만드는 절차


  1. ptf

  2. fdataclass

  3. 필요한 factory 메서드 만들기

dio provier 만들기 절차


  1. riverpodpart

  2. riverpod으로 provider만들기

  3. 아래 문법 처럼


Dio dio(DioRef ref) {
return Dio(BaseOptions(baseUrl: '[Target URL]'));
}

Enum based에서 값 요청 provider 만드는 절차


  1. riverpodpartriverpodclass로 초기 설정 후 이름 설정

  2. build 함수 타입 정해주고 return 값 정해주기

  3. 여러 상태 컨트롤하는 함수 만들기(loading, error, success 등등)

  4. 흐름을 적자면 먼저 state의 status를 loading으로 바꿔주고 Get요청을 날린다음에 성공적으로 값을 가져오면 fromJson해가지구 copyWith해서 status success와 값을 가져온 값으로 변경을 한다. 에러가 난다면 copyWith해서 status failure와 error에 e.toString 넣어줌

특이점 정리

  • copyWith로 값 변경
  • single state class임

inistate modifier 주의할 점

페이지에 진입했을 때 바로 api를 쏘는 동작을 하고 싶어서 initState에 api 통신하는 코드를 넣었다. 이 때 UI를 수정하는 동작도 포함돼있어서

FlutterError (Tried to modify a provider while the widget tree was building.... 에러가 났다.

  
void initState() {
super.initState();
ref.read(enumActivityProvider.notifier).fetchActivity(activityTypes[0]);
}

// 이거를 아래로 수정


void initState() {
super.initState();
Future.delayed(Duration.zero, () {
ref.read(enumActivityProvider.notifier).fetchActivity(activityTypes[0]);
});
}

Future.delayed로 감싸면 에러가 해결된다. 이걸 사용하면 비동기적으로 실행하는듯 그래서 UI 그려지기 까지 기다려주는 것 같음.

Sealed class based


특징은 multi class 이다.

절차

  1. sealed class를 만든다.
sealed class StateClass {
const StateClass();
}
  1. StateClass를 extend하여 init, loading, success, fail을 구현한다.
final class StateClassInit extends StateClass {
const StateClassInit();


String toString() => 'StateClass()';
}
  1. provider에서 state 할당할 때 copyWith을 사용하는게 아니라 구현한 class를 사용한다. 나머지는 비슷

async처럼 보이게하는 notifier


enum

setInit 대신에 provider에서 build함수 안에 fetch해주면 된다.

  
StateClass build() {
fetchActivity(types[0]);
return StateClass.init();
}

주의할 점

이 때도 위처럼만 해버리면 에러가난다.

future.delayed로 감싸는 것이 아닌 state에 init()을 먼저 할당해준다.

  
StateClass build() {
state = StateClass.init();
fetchActivity(types[0]);
return StateClass.init();
}

AsyncNotifier


방식은 Notifier와 비슷하지만 안에 생성하는 메서드들은 모두 Future<void>타입이다.

AsyncValue가 좋은게 AsyncLoading에서 이전값을 가지고 있고 AsyncData에서 바뀐 next값을 가지고 있다.

AsyncError에서도 value에는 이전값 보유한다.

guard

Async할 때 try catch문에서 보통 try에 AsyncData, catch에선 AsyncError을 정의합니다. 이를 간소화시켜주는 방법입니다.

state = await AsyncValue.guard(() async {
await wait();
return state.value! + 1;
});

이렇게 하면 알아서 AsyncData와 AsyncError에 값을 넣어준다.

riverpod annotation 절차

  1. riverpodpart

  2. riverpodAsyncClass / 작명 / build함수 타입 지정

  3. build에 async 추가 및 family 할거면 build에 인자 추가

  4. 추가 값들 추가

FutureOr<int> build({required int init}) async {

return init;
}

riverpod annotation 사용하면 named parameter 사용할 수 있다.

skip error

AsyncValuewhen 메서드 사용 시 skipError 속성이 있다.

이 속성은 default가 false고 true로 바꾸면 에러 발생 시 error: 로직에 처리된게 실행되는게 아닌 data가 있다면 이전 데이터를 보여주는 것이다.

· 2 min read
Park Ki Hyun

ListView


ListView 위젯을 Column/Row위젯 아래에 둔다면 ListViewColumn/Row의 사이즈들이 unbounded height가 일어나서 render overflow가 납니다.

Column/Row 아래에 ListView를 두고 싶다면 Expanded 위젯으로 ListView를 감싸야 함.

Notifier Provider


생성

notifier를 입력해서 먼저 notifier를 정의한다.

class CounterNotifier extends Notifier<int> {

int build() {
return 0;
}
}

CounterNotifier는 Notifier의 이름이고 Notifier<int>로 돼있는데 int는 반환 타입이다. build함수 앞에는 반환 타입을 붙여줘야 한다.

StateNotifierChangeNotifier를 사용하면 ref를 사용할 수 없지만(사용하려면 생성자에서 정의해줘야 한다.) Notifier를 사용하면 ref를 바로 사용할 수 있다.

tear-offs 지원

onPressed:(){
func();
}

# 위와 아래는 같다.

onPressed: func,

위 코드와 같은 동작을 하는 것이 tear-offs다.

거추장스러운 부분이 제거가 된다. 함수와 생성자에서 사용할 수 있다.

그래서 아래와 같이 notifierProvider를 선언할 수 있다.

final counterProvider = NotifierProvider<Counter, int>(() {
return Counter();
});

#위를 아래처럼

final counterProvider = NotifierProvider<Counter, int>(Counter.new);

모르면 일단 외우자 tear-offs

접근

watch하면 state값을 가져올 수 있고 인스턴스에 접근하려면

ref.raed(counterProvider.notifier).increment();

이렇게 할 수 있다.

· One min read
Park Ki Hyun

invalidate


onPressed: () async {
ref.invalidate(userDetailProvider);
},

refresh할 때 invalidate를 사용한다면 위처럼 onPressed 함수를 구현할 수 있습니다. 물론 skipLoadingOnRefresh도 적용했습니다.

invalidate의 인자값 타입은 ProviderOrFamily이다. 그래서 Family를 주지 않고 적용한다면 모든 provider가 auto dispose된다.

refresh


onPressed: () async {
return ref.refresh(userDetailProvider(userId));
},

또는

onPressed: () {
return ref.refresh(userDetailProvider(userId).future);
},

refresh 인자 타입은 Family Provider라면 family값을 넘겨주어야 한다. 그리고 return을 사용해야 하기에 return값이 없다면 invalidate를 추천한다.