Skip to main content

27 posts tagged with "flutter"

View All Tags

· 2 min read
Park Ki Hyun

nullable constructor


실수한 코드

처음에 class의 인스턴스를 만들 때 nullable로 선언하면 될 줄 알았다.

class NaviState {
final int? index;
final bool? yesOrNo;

NaviState({
this.index,
this.yesOrNo,
});
}

이렇게 했더니 사용하는 변수 타입이 nullabe이 되어버려서 계속 사용하는 변수 뒤에 !를 붙여줘서 null이 아님을 명시했어야 했는데

원했던 코드

class NaviState {
final int index;
final bool yesOrNo;

NaviState({
int? index,
bool? yesOrNo,
}) : index = index ?? 0,
yesOrNo = yesOrNo ?? false;
}

이렇게 nullable로 선언하는 것이 아닌 생성자에서 nullable로 받고 null인 경우 초기값을 설정하도록 하는 것이 내가 의도한 방향성과 더 맞는 코드인 것을 알게 됐다.


flutter에서 라우팅 하는 방법은 좀 다양한 것 같다.

그 중 Navigator.pushNamed이다.

Navigator.pushNamed로 context에 불러올 위젯 이름을 push한다.

그러면 정의해 놓은 onGenerateRoute를 통해서 page를 불러오면 된다. 간단하다.

GestureDetector 렌더링 문제


갑자기 GestureDetector만 사용하면 렌더링 에러가 났다.

GestureDetector에 사이즈를 제공해주지 않았기 때문이라고 한다. (ListView도 마찬가지다)

Expanded(
child: SizedBox(
height: 200,
width: 200,
child: GestureDetector(),
),
)

이런식으로 구현할 수 있다.

· 3 min read
Park Ki Hyun

선택적 매개변수


Color getColor(WidgetRef ref, bool isInactive, [Color? color]) {}

위와 같은 함수가 있다고 가정하자. [Color? color] 이렇게 대괄호로 받는 매개변수는 넣어도 되고 안 넣어도 되는 선택적 매개변수이다.

svg icon에 대해서


지금 저는 IconButton을 사용하지 않고 Custom으로 Button위젯을 만들어서 사용하고 있습니다.

이 때 버튼 등 다양한 값들을 GestureDetectorRow 위젯으로 감싸아서 했는데 width가 넘어갔다는고 하네요.. 그래서 appbar가 좀 밀려보였습니다.

svg파일의 크기를 줄여야 합니다. vs code에서 위젯트리 디버깅 확인해보며 Row에 할당된 width를 확인하고 그 이하로 svg 파일 크기를 줄이면 해결됩니다.


navigationRail을 사용하면 사이드 네비게이션 바를 사용할 수 있다.

NavigationRail안에 NavigationRailDestination으로 Navigator를 하나하나 정의할 수 있다.

selectedIndex가 필수로 필요하다.

onDestinationSelected로 어떤 것을 선택했는지 정의할 수 있다. setState로도 할 수 있는데 provider를 사용해서 watch해서 사용할 수도 있다.

예를 들어서 selectedIndex를 state를 watch하고 onDestinationSelected에는 값을 바꿔주는 함수를 정의해서 read로 호출하면 될 듯

selectedIconThemeunselectedIconTheme을 통하여 테마 지정 가능 아직은 큰 필요성 모르겠다.

trailing을 사용하면 맨 밑에 버튼 같은 걸 만들 수 있습니다.

이 과정도 복잡했는데 Expanded와 Align 을 사용해야 할 수 있었습니다. 이에 대해서는 또 찾아봐야겠네요

body에 border-radius


사실 body에 border-radius줘도 안바뀐다 왜냐하면 배경색과 색이 같아서

그래서 body를 이루고 있는 Row위젯을 ColorBox로 감쌉니다. 그렇게 배경색을 주고 밑에서 Container에 해당했던 부분(기존에 border-radius를 줬던 부분)을 Expanded로 확장시킵니다 그럼 끝 !

· 2 min read
Park Ki Hyun

NotifierProvider의 notifier 메소드


final cartProvider = NotifierProvider<RiverpodCart, List<Product>>(() {
return RiverpodCart();
});

플러터는 위처럼 provider를 위젯트리에 따로 등록하지 않고 변수로 선언해서 사용한다.

return ProductTile(
product: product,
isInCart: ref.watch(cartProvider).contains(product),
onPressed: ref.read(cartProvider.notifier).onProductPressed,
);

신기한 것이 있었다. 이렇게 전역변수로 선언한 것을 watch하고 read하는 것은 이해를 했는데 갑자기 .notifier.. 이건 뭐지

실험해보고 찾아보니 .notifier를 붙이면 해당 provider의 class로 접근해서 그 class의 메소드 등을 사용할 수 있다는 것.

즉 위에서 cartProvider의 NotifierProvider<RiverpodCart, List<Product>> 중 앞 부분인 RiverpodCart에 접근하는 느낌이다.

.notifier를 붙이지 않으면 이 provider의 상태로 접근하는 것이다.

List<Product>에 접근한다. 그래서 contians 메소드를 사용할 수 있었다.

dart 생성자


named parameter

생성자 안에 {} 중괄호로 파라미터를 묶으면 named parameter 가능

named parameter하면 required 할 수도 있다.

· 7 min read
Park Ki Hyun

const에 대해


class에서 생성자를 const로 만들지 않았을 때

class Product {
final int id;
final String name;
final String desc;
final String price;
final String imageUrl;

Product({
required this.id,
required this.name,
required this.desc,
required this.price,
required this.imageUrl,
});
}

위처럼 만들면 아래 코드는 에러난다.

const List<Product> storeProductList = [];

생성자 Product에 const를 붙여주면 해결할 수 있다.

context.read에 대해


void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => MyCart(),
),
ChangeNotifierProvider(
create: (context) => MyBadge(myCart: context.read()),
),
],
child: MaterialApp(
home: MyApp(),
),
),
);
}

코드 상황이 위와 같을 때 어떻게 MyCart의 정보를 contex.read()로 가져오나 의문이 있었다.

본론부터 말하면

        ChangeNotifierProvider(
create: (context) => MyBadge(myCart: context.read<MyCart>()),
),

위와 같이 사용하면 헷갈릴 일이 없고 가져올 수 있는 이유는 ChangeNotifierProvider를 통해서 MyCart를 위젯 트리 상 등록 했기에 바로 가져와지는 것이다. 만약 중간에 MyStore 같은 다른 위젯이 있었다면 MyCart는 저런식으로 가져오지 못한다.

List의 값에 const에 대해


List<Product> cartProductList = const [];

리스트의 불변을 보장해주기 위해 []앞에 const 키워드를 붙일 수 있다. List<Product> 앞에 붙이는 것과는 다르다. 리스트를 바꿀 수 있지만(요소 말고) 리스트에 직접적인 값을추가하거나 삭제 수정은 못한다.

삭제를 원한다면 where를 사용할 수 있다.

list.where((p)=>p!= product).toList();

위처럼 작성하면 list를 돌면서 돌고 있는 값이 p인데 product와 비교하면서 product는 빼고 다시 리스트를 만드는 코드이다.

추가를 원한다면

[...list,product]

아래처럼 간단하게 ...연산자를 사용할 수 있다.

dart 자동 매개변수..?


  void onProductPressed(Product product) {
if (cartProductList.contains(product)) {
cartProductList = cartProductList.where((p) => p != product).toList();
} else {
cartProductList = [...cartProductList, product];
}
notifyListeners();
}

이렇게 매개변수가 필요한 함수로 구현돼있는데

      return ProductTile(
product: product,
isInCart: providerCart.cartProductList.contains(product),
onPressed: providerCart.onProductPressed,
);

이렇게 사용하길래 깜짝 놀랐다. 뭔가 싶었는데 ProductTile class에 비밀이 있었다.

  final Product product;
final bool isInCart;
final void Function(Product product) onPressed;

이렇게 onPressed가 구현돼있으면 product를 넣지 않아도 알아서 넣어준다고..

product를 넣지 않았는데 어떻게 아냐구요? ProductTile 위젯을 만들 때 product값을 전달하기 때문에 인지할 수 있다고 하네요 !

정리하자면 특정 위젯에 a라는 값을 통해서 구현하고 그 a라는 값을 매개변수로 사용하는 함수가 있을 때 다른 함수도 그 a라는 값을 필요로 한다면 사용하지 않는다.. 라는 것입니다. 근데 100% 이해를 못했다. 마음에 담아두자.

context.read() 2


한 가지를 더 이해해 버렸다.

위젯 트리 상에 등록해서 context로 가져올 수 있다면

context.read<ProviderCart>().onProductPressed

위 처럼 ProviderCart의 함수를 가져올 수 있다.

context.select()


이거 좀 이상하긴 하다. 그래서 이제 완벽히 이해했다.

context.watch() : Provider의 모든 상태를 구독한다.

context.select(): Provider의 특정 상태를 구독한다.

final count = context.select<Counter,int>((counter) => counter.count);

위 처럼 사용하기 때문에 select는 특정 상태 구독 가능

context.read(): 구독안하고 현재 상태 그냥 가져오기

드디어 context 3형제는 이해한듯

Riverpod


provider 만든 사람이 만든거라고 해서 provider랑 비슷할 줄 알았는데 다르다.

provider 등록을 MultiProvider에 등록하는 것이 아닌 그냥 전역 변수로 등록한다. 별도 변수에 할당하기 때문에 같은 Provider를 다른 변수에 할당할 수 있다. 그리고 BuildContex를 사용하지 않고 별도의 WidgetRef라는 것을 사용한다.

변경점 정리
  1. provider를 전역 변수로 선언해서 사용
  2. BuildContext 대신 WidgetRef 사용 :::

나머지는 차차 알아가보는걸로

StatelessWidgetConsumerWidget으로 바꿔야한다. 그리고 build()메소드 파라미터에 WidgetRef ref를 추가해야 ref사용할 수 있다.

StatefulWidgetConsumerStatefulWidget으로 바꿔야 한다.

바꾸기 싫으면 Consumer 위젯으로 감싸서 사용할 수 있다.

provider 종류

  1. StateProvider : 별로 클래스 선언 없이 가볍게 사용할 수 있다.

  2. NotifierProvider : 클래스를 만들어서 사용할 수 있습니다.

  3. Provider : 기존 Provider의 Provider와 같음 즉 상태가 없는 Provider들

잡기술

  1. AutoDispose : NotifierProvider뒤에 .autoDispose를 붙이고 Notifier extends한 것을 AutoDisposeNotifier로 수정하면 됩니다. 자동으로 dispose 해줍니다.

  2. family : NotifierProvider뒤에 family를 추가해서 사용하면 되느데 매개변수 타입을 추가해주면 build()에 매개변수 추가해서 사용할 수 있습니다.

  3. AutoDisposeFamilyNotifier : 1,2번을 합친 것.

Error: Unable to find git in your PATH.


간혹 flutter를 처음할 때 아래와 같은 에러가 괴롭힌다.

Error: Unable to find git in your PATH.
git config --global --add safe.directory '*'

위 명령어로 해결할 수 있음 저장소 권한 문제인 것같다. 관리자 powershell은 flutter 됐었음

· 3 min read
Park Ki Hyun

일단 vs code 확장은 bloc, dart data class generator, dart-import, pubspec assist

dart data class generator가 있으면 코드 자동 생성을 편하게 해준다. (copyWith, constructor 등.. )

pubspec assist가 있으면 패키지를 쉽게 넣을 수 있음

Bloc에 대해서


영상을 보며 따라하면서 저번주에 KOSTA에서 배운 Bloc 사용법에 대해 익히고 있었다.

영상은 다른 방식이라 헷갈렸는데 VS Code의 Bloc extension을 사용하여 New Bloc을 해가지구 Bloc을 만들었다.

3개의 파일이 나온다. bloc,event,state

state

class TasksState extends Equatable {
const TasksState({
this.allTasks = const <Task>[],
});

final List<Task> allTasks;


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

현재 state에는 List<Task> 타입의 allTasks라는 상태가 하나 있다.

여러 개 추가하면 여러 개 되는듯?

event

sealed class TasksEvent extends Equatable {
const TasksEvent();


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

class AddTask extends TasksEvent {
final Task task;
const AddTask({
required this.task,
});


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

class UpdateTask extends TasksEvent {
final Task task;
const UpdateTask({
required this.task,
});


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

class DeleteTask extends TasksEvent {
final Task task;
const DeleteTask({
required this.task,
});


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

이렇게 서비스들 만들 수 있다. 현재 전부 구현된건 아닌.. 거같은데 일단 이따 수정

bloc

하다가 깨달았다.

class TasksBloc extends Bloc<TasksEvent, TasksState> {
TasksBloc() : super(const TasksState()) {
on<AddTask>(_onAddTask);
}

void _onAddTask(AddTask event, emit) {
emit(TasksState(
allTasks: [...state.allTasks, event.task],
));
}
}

Bloc을 extends 할 때 Bloc<event, state>로 하라고 배웠다. 근데 할 때 state를 intbool같은 타입으로만 지정해줘서 그냥 state로 사용하면 됐었는데 class로 state 타입 지정해주니 state.allTasks로 해야 하더라..

깨달음

bloc에서 큰 실수가 있었다. 위코드 처럼 작성하면 되는데 코드 수를 줄여보겠다고

  void _onAddTask(AddTask event, emit) {
emit(
[...state.allTasks, event.task],
);
}

위처럼 작성했다가 작동을 안해서 살짝 어지러웠다.

Bloc이 Bloc<TasksEvent,TasksState>이니 TasksState type에 맞춰줘야 하나보다.

· 2 min read
Park Ki Hyun

관심사 분리


모듈 별 경계를 나누고 모듈간 약속을 정하는 것 like a 전기 콘센트와 플러그의 관계라고 한다.

MVVM 이론


ViewViewModel 그리고 Model을 나누어 View를 쉽게 변경할 수 있는 목적을 갖고 있는 아키텍처 패턴

역할

View : UI
ViewModel: View 상태 및 로직 담당
Model: 비지니스 로직과 데이터 입출력 담당

핵심은 데이터 바인딩이다.

데이터 바인딩이란 간단히 말하면 ViewModel과 View가 서로를 의존하지 않는 것 Provider 패키지를 사용해서 데이터 바인딩 이용한다는데 나는 Bloc를 사용해서 하고 싶다.. 당장은 Provider로 구현해보기

페이지 마다 하나의 ViewModel이 있어야 하고 ViewModel은 View의 상태와 로직을 관리한다.

Model은 Service, Model, Repository가 있는데 Service는 전역 상태와 비지니스 로직을 관리하고 Model은 데이터 Repository는 데이터 요청 로직을 담당한다.

걍 뭔가 어려움 많이 연습해봐야겠다.

· 5 min read
Park Ki Hyun

테스트 코드 작성 요령

테스트 코드를 작성하면 기능들의 의존성도 확인할 수 있고 테스트할 기능이 많아질 수록 자동화 해놓면 좋다.

테스트 종류

단위 테스트 ( Unit Test ): 특정 함수 및 클래스 테스트
위젯 테스트 ( Widget Test ): 단일 위젯 테스트
통합 테스트 ( Integration Test ): 앱의 전체적인 테스트 동작

단위 테스트


내장 테스트로 테스트를 진행할 수 있습니다.

테스트 파일은 파일이름_test.dart여야 한다. 즉 _test.dart로 끝나야 함

테스트 관련 함수

test(): 테스트 시작 expect(): 값 검증
group(): 테스트 그룹 만들기
setUp(): 테스트 시작 전 실행하는 함수(계속 테스트 시작할 때마다 실행)
setUpAll(): 최초 한 번만 실행
tearDown(): 테스트 후 실행
tearDownAll(): 테스트 종료 후 마지막 한 번 실행

테스트를 하다보면 왜하나 싶을 수 있는데 변경에 대처하기 위해서 작성하는 것임. 완전 변한다면 또 의미가 없어지긴하는데 리팩토링 했을 때 검증하는 느낌이려나 아직은 잘 모르겠다.

Mock Object

api 통신 테스트 같은 경우 외부 변수가 많아서 mock object로 한다.

왜냐하면 실제 요청 응답은 변수가 많아서... 재현이 어렵다. 그리고 실제로 서버 응답 실패 테스트 같은 경우도 쉽지 않다. mock object를 사용하면 쉽게 할 수 있다.

mockito랑 build_runner를 사용한다고 한다.

mockito로 어노테이션 달고 build_runner로 코드 생성하는 거임

DI ( Dependency Injection )

di를 하는데 생성자를 통해 외부 코드를 받는다인 간단한 개념이라고 한다.

위젯 테스트


상호작용, 생김새, 상태 확인을 테스트 한다고 한다.

위젯 테스트

testWidgets(): 테스트 진행
pump(): 화면 갱신
find.text(): 특정 테스트 검색
find.byType(): 특정 클래스의 타입과 일치하는 위젯 검색
tester.widget(): 특정 타입의 위젯 가져옴
tester.element(): 특정 위젯의 BuildContext 가져옴

golden test도 있다.

golden test는 이미지 픽셀 비교하는 테스트다 즉 시각적 모습을 테스트할 수 있다.

flutter test --update-goldens

이걸로 골든테스트 정답 생성해줌 신기하넹.

통합 테스트


GUI테스트 EndToEnd라고 합니다.

실제 에뮬레이터를 통해서 테스트를 하게 된다.

integration_test를 추가해야 한다고 합니다.


integration_test:
sdk: flutter

dev_dependecies에 붙여넣어주면 된다.

integration_test라는 폴더를 root에 만들어주고 하면 된다. 아주 신기하다.

테스트 커버리지


플러터 커버리지

테스트 코드 기반으로 커버리지 측정을 한다.

커버리지 거터스

이거는 특정 파일에서 좌측 하단 watch를 눌러보면 어디가 테스트가 안됐는지와 커버리지 % 알려준다.

hydrated bloc을 사용해보거나 다른 서드파티들 검색해서 찾아보자