inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

Flutter 앱 개발 실전

Riverpod 이론1

NotifierProvider 로 변경하고 싶어요

해결된 질문

371

Link

작성한 질문수 15

0

안녕하세요.

Riverpod 상태관리에서 StateNotiferProvider 대신에 NotifierProvider를 사용하는 것을 권장하신다고 하셨는데요.

StateNotifierProvider 로 작성한 코드를 NotiferProvider 로 변경하고 싶은데 잘 안되어 문의 드립니다.

 

contactStateProvier 코드로는 정상 동작됩니다.

이 코드를 NotifierProvider 로 변경해보려고 하는데 잘 안되네요.

final contactStateProvider =
    StateNotifierProvider<ContactStateNotifier, ContactResultBase>(
  (ref) {
    final repository = ref.watch(restClientProvider);
    final notifier = ContactStateNotifier(restClient: repository);
    return notifier;
  },
);

class ContactStateNotifier extends StateNotifier<ContactResultBase> {
  final RestClient restClient;

  ContactStateNotifier({required this.restClient})
      : super(ContactResultLoading()) {
    postContactList();
  }

  postContactList() async {
    final resp = await restClient.postContactList(
        Crypto.AES_encrypt(Crypto.URLkey()), '');
    state = resp;
  }
}
@RestApi(baseUrl: RetrofitURL.baseUrl)
abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

  @POST(RetrofitURL.mLogin)
  @FormUrlEncoded()
  Future<LoginResponse> userLogin(
      @Field() String keyword,
      @Field() String userID,
      @Field() String password,
      @Field() String uID,
      @Field() String mfoneNO,
      );

  @POST(RetrofitURL.contactData)
  @FormUrlEncoded()
  Future<ContactResult> postContactList(
      @Field() String keyword,
      @Field() String search,
      );
}
final secureStorageProvider =
    Provider<FlutterSecureStorage>((ref) => const FlutterSecureStorage());

final dioProvider = Provider<Dio>((ref) {
    final dio = Dio();

    final storage = ref.watch(secureStorageProvider);

    dio.interceptors.add(LogInterceptor());
    dio.interceptors.add(
        CustLogInterceptor(storage: storage,),
    );
    return dio;
});

final restClientProvider = Provider<RestClient>((ref) {
    final dio = ref.watch(dioProvider);
    final repository = RestClient(dio);
    return repository;
});

 

contactStateProvider 를 변경시도 해보다가 에러가 발생한 코드

final contactProvider =
    NotifierProvider<ContactNotifier, ContactResultBase>(
  () {
    final repository = ref.watch(restClientProvider);
    final notifier = ContactNotifier(restClient: repository);
    return notifier;
  },
);

class ContactNotifier extends Notifier<ContactResultBase> {
  @override
  ContactResultBase build() => ContactResultLoading();

  final RestClient restClient;

  ContactStateNotifier({required this.restClient}) {
    postContactList();
  }

  postContactList() async {
    final resp = await restClient.postContactList(
        Crypto.AES_encrypt(Crypto.URLkey()), '');
    state = resp;
  }
}

 

flutter

답변 1

0

DevStory

안녕하세요

 

첨부해 주신 코드상에서 확인할 수 있는 내용에 대해 피드백을 드립니다.

  1. 상태 변경을 청취하여 새로고침을 할 필요가 없는 경우 ref.watch가 아니라 ref.read로 사용하시는게 좋습니다.

final secureStorageProvider =
    Provider<FlutterSecureStorage>((ref) => const FlutterSecureStorage());

final dioProvider = Provider<Dio>((ref) {
    final dio = Dio();

    final storage = ref.read(secureStorageProvider);

    dio.interceptors.add(LogInterceptor());
    dio.interceptors.add(
        CustLogInterceptor(storage: storage,),
    );
    return dio;
});

final restClientProvider = Provider<RestClient>((ref) {
    final dio = ref.read(dioProvider);
    final repository = RestClient(dio);
    return repository;
});

 

  1. Notifier의 경우 ref.watch 코드를 build() 함수 안에서 다룬다는 점이 StateNotifier와 다릅니다. 자세한 예제는 관련 공식 문서 링크를 참고해 주세요.

final contactProvider =
    NotifierProvider<ContactNotifier, ContactResultBase>(ContactNotifier.new);

class ContactNotifier extends Notifier<ContactResultBase> {
  @override
  ContactResultBase build() {
    return ContactResultLoading();
  }

  postContactList() async {
    final resp = await ref.read(restClientProvider).postContactList(
        Crypto.AES_encrypt(Crypto.URLkey()), '');
    state = resp;
  }
}

 

새로운 코드를 실행했을 때 발생하는 에러 코드 메세지 및 관련 코드를 공유해 주셔야 정확한 원인 분석 및 피드백을 드릴 수 있을 것 같습니다.

0

Link

답변 감사드립니다.

알려주신 코드로 변경하여 시도했더니 아래와 같은 에러메시지가 나옵니다.

abstract class ContactResultBase {}

class ContactResultLoading extends ContactResultBase {}

class ContactResultError extends ContactResultBase {
  final String errMsg;

  ContactResultError({
    required this.errMsg,
  });
}

@JsonSerializable()
class ContactResult extends ContactResultBase {
  final String status;
  final String message;
  final List<ContactItem>? addrinfo;

  ContactResult({
    required this.status,
    required this.message,
    this.addrinfo,
  });

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

  Map<String, dynamic> toJson() => _$ContactResultToJson(this);
}
class ContactResultProviderPage extends ConsumerWidget {
  const ContactResultProviderPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ContactResultBase data = ref.watch(contactProvider);
    final postdata = ref.watch(contactProvider.notifier).postContactList();

    if (data is ContactResultLoading) {
      return Center(
        child: CircularProgressIndicator(),
      );
    }

    final ids = postdata as ContactResult;

    if (ids.status.contains("success")) {
      final addrinfo = ids.addrinfo as List<ContactItem>;
      return _ListViewSubPage(posts: addrinfo);
    } else {
      return _ErrorSubPage(
        status: ids.status,
        message: ids.message,
      );
    }
  }
}
final ids = postdata as ContactResult;

여기서 Casting 에러가 발생합니다.

The following TypeError was thrown building ContactResultProviderPage(dirty, dependencies: [UncontrolledProviderScope], state: ConsumerState#4877c):

type 'Future<dynamic>' is not a subtype of type 'ContactResult' in type cast

1

DevStory

먼저 에러가 발생하는 이유는 postContactList() 함수는 아무것도 반환하지 않는데, postdata라는 변수에 결과를 할당한 뒤 뒤쪽에서 ContactResult 타입으로 캐스팅하려 했기 때문에 에러가 발생합니다.

  postContactList() async {
    final resp = await ref.read(restClientProvider).postContactList(
        Crypto.AES_encrypt(Crypto.URLkey()), '');
    state = resp;
  }
final postdata = ref.watch(contactProvider.notifier).postContactList();

 

그 외에도 postContactList() 함수를 build() 함수 내에서 watch로 접근하여 호출하고 있는데, 그렇게 되면 ContactNotifier에서 상태가 변경될 때 마다 build() 함수가 다시 실행되기 때문에 꼬리에 꼬리를 무는 형태로 postContactList() 함수가 무한 반복 호출될 수 있습니다.

ContactResultProviderPage 클래스를 StatefulWidget으로 변경한 뒤 initState() 함수에서 postContactList() 함수를 호출하도록 변경하시는 것을 권장드립니다.

 

추가로 ContactNotifier의 상태 클래스에 Dart sealed 키워드를 활용하면 상태 클래스를 열거 가능한 하위 클래스들로 다루어 switch 구문에서 default 값 없이 사용할 수 있습니다.

 

위 문제들을 해결한 샘플 코드를 다음 DartPad 링크에서 확인해 주세요.

감사합니다 :)

1

Link

정말 감사드립니다.

구글 검색으로 찾은 간단한 예제들로는 이해가 잘 안되었습니다.

덕분에 상태관리에 대해 조금 이해하게 되었습니다.

알려주신 코드 덕분에 아래와 같이 수정하여 해결하였습니다.

final contactProvider =
    NotifierProvider<ContactNotifier, ContactResultBase>(ContactNotifier.new);

class ContactNotifier extends Notifier<ContactResultBase> {
  @override
  ContactResultBase build() {
    return ContactResultLoading();
    // 초기화 시 state는 로딩 상태
  }

  void postContactList() async {
    ContactResult resp = await ref
        .read(restClientProvider)
        .postContactList(Crypto.AES_encrypt(Crypto.URLkey()), '');
    state = resp;
    // ConsumerStatefullWidget에서 초기화시 호출하여 state를 ContactResult 로 만듬
  }
}
class ContactResultProviderPage extends ConsumerStatefulWidget {
  const ContactResultProviderPage({super.key});

  @override
  ConsumerState<ContactResultProviderPage> createState() =>
      _ContactResultProviderPageState();
}

class _ContactResultProviderPageState extends ConsumerState<ContactResultProviderPage> {

  @override
  void initState() {
    super.initState();
    ref.read(contactProvider.notifier).postContactList();
  }

  @override
  Widget build(BuildContext context) {
    final data = ref.watch(contactProvider);

    if (data is ContactResultLoading) {
      return Center(
        child: CircularProgressIndicator(),
      );
    }

    final ids = data as ContactResult;

    if (ids.status.contains("success")) {
      final addrinfo = ids.addrinfo as List<ContactItem>;
      return _ListViewSubPage(posts: addrinfo);
    } else {
      return _ErrorSubPage(
        status: ids.status,
        message: ids.message,
      );
    }
  }
}

수강 기한 연장 요청드려도될까요..

1

48

2

37.provider 실습 문제점, 카트에서 상품이 지워지지 않습니다.

1

75

2

다트 프로젝트

1

51

2

context.read<LangService>().toggleLang 해도 언어가 변경되는 이유

1

74

3

수강 기간 연장 신청 요청드립니다.

1

68

3

수강기간 연장 부탁드립니다.

1

58

3

제공해주신 flutter_design_system 라이브러리 질문입니다.

1

53

2

수강 기간 연장 부탁드립니다

1

52

2

수강 기한 연장

1

78

3

강의 잘 보고있습니다!

1

59

2

애뮬레이터 실행 오류

1

69

2

pdf 강의노트

1

62

2

수강기간 연장 부탁드립니다.

1

86

2

수강 기간 연장 요청

1

86

2

수강기간 연장 부탁드립니다

1

129

2

코드 생성기 - build runner 관련 오류

1

110

1

디자인 시스템 구성에 대해 질문 드립니다

2

145

2

CartItem 추가시

1

95

2

const 커스텀클래스

1

95

1

강의 수강 기간 연장 요청드립니다.

1

127

2

코드 생성기 - 실습 build runner 안 되는 분.

1

270

2

Flutter 강의자료 열리지 않는 문제

1

165

2

riverpod 프로젝트에 궁금한점이 있어 질문 남깁니다.

1

123

2

수강 기강 연장 부탁드리겠습니다! :ㅇ

1

88

2