인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

Link님의 프로필 이미지
Link

작성한 질문수

Flutter 앱 개발 실전

Riverpod 이론1

NotifierProvider 로 변경하고 싶어요

해결된 질문

작성

·

306

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;
  }
}

 

답변 1

0

DevStory님의 프로필 이미지
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;
  }
}

 

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

Link님의 프로필 이미지
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

DevStory님의 프로필 이미지
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 링크에서 확인해 주세요.

감사합니다 :)

Link님의 프로필 이미지
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,
      );
    }
  }
}
Link님의 프로필 이미지
Link

작성한 질문수

질문하기