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

limazero14님의 프로필 이미지
limazero14

작성한 질문수

[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!

일반 Navigation에서 GoRouter로 전환하기

115강 5분 10초, model의 타입을 ProductModel로 지정했을 때 발생하는 타입 및 null 에러가 있습니다

해결된 질문

작성

·

286

0

class ProductPage extends StatelessWidget {
  const ProductPage({super.key});

  @override
  Widget build(BuildContext context) {
    return PaginationListView<ProductModel>(
      provider: productProvider,
      itemBuilder: <ProductModel>(_, index, ProductModel model) =>
          GestureDetector(
        onTap: () => context.goNamed(
          RestaurantDetailPage.routeName,
          pathParameters: {
            'rid': model.restaurant.id,
          },
        ),
        child: ProductCard.fromProductModel(
          model: model,
        ),
      ),
    );
  }
}

여기서 이상한 에러가 뜹니다.

 

먼저 'rid': model.restaurant.id 이 부분에서는 아래와 같은 에러가 뜹니다.

The property 'restaurant' can't be unconditionally accessed because the receiver can be 'null'.
Try making the access conditional (using '?.') or adding a null check to the target ('!').dartunchecked_use_of_nullable_value

 

그리고 두 번째로, ProductCard.fromProductModel(model: model)에서는 이런 에러가 뜹니다.

The argument type 'ProductModel' can't be assigned to the parameter type 'ProductModel'.dartargument_type_not_assignable

 

두 에러 모두 이해되지 않습니다. ProductModel 타입을 ProductModel 타입으로 Assign할 수 없다니요? 같은 타입인데 이런 에러가 뜹니다.

 

또한 첫 번째 에러의 경우에도, 분명히 nullable 타입이 존재하지 않는데 nullable 체크를 하라고 하고 있습니다. 당황스럽습니다.

 

한편, 강의는 이런 식으로 되어 있습니다.

itemBuilder: <ProductModel>(_, index, model)

model의 타입을 따로 지정해주지 않았습니다. 이렇게 했을경우 model의 타입은 dynamic이 되며, 자동 완성 기능은 수행할 수 없지만, 결론적으로 잘 작동은 합니다.

 

그런데 왜 저런 이상한 에러가 발생하는 지 모르겠습니다.

 

여기 이와 관련된 코드를 덧붙입니다. 그러나 대부분 강의와 동일합니다.

@JsonSerializable()
class ProductModel implements IModelWithId {
  @override
  final String id;

  /// 상품 이름
  final String name;

  /// 상품 상세 정보
  final String detail;

  /// 상품 이미지 URL
  @JsonKey(fromJson: DataUtils.pathToUrl)
  final String imgUrl;

  /// 상품 가격
  final int price;

  /// 레스토랑 정보
  final RestaurantModel restaurant;

  ProductModel({
    required this.id,
    required this.name,
    required this.detail,
    required this.imgUrl,
    required this.price,
    required this.restaurant,
  });

  factory ProductModel.fromJson(Map<String, dynamic> json) =>
      _$ProductModelFromJson(json);
}
typedef PaginationWidgetBuilder<T extends IModelWithId> = Widget Function(
  BuildContext context,
  int index,
  T model,
);

문제가 발생하고 있는 스크린샷:

스크린샷 2023-08-10 오전 7.48.15.png스크린샷 2023-08-10 오전 7.48.36.png

답변 1

0

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

안녕하세요!

혹시 PaginationListView 클래스의 State 제너릭에 타입 선언을 추가해주면 문제가 해결되시나요?

아니라면 다시 질문 주세요!

감사합니다!

limazero14님의 프로필 이미지
limazero14
질문자

빠른 답변 감사드립니다!

현재 제 PaginationListView 클래스의 상황입니다. 강의랑 최대한 비슷하게 한다고 했는데, 제네릭에서 미스가 났는지도 모르겠네요. 그러나 일단은, 필요한 T는 모두 extends하고 있습니다.

typedef PaginationWidgetBuilder<T extends IModelWithId> = Widget Function(
  BuildContext context,
  int index,
  T model,
);

class PaginationListView<T extends IModelWithId>
    extends ConsumerStatefulWidget {
  final StateNotifierProvider<PaginationProvider, CursorPaginationBase>
      provider;
  final PaginationWidgetBuilder<T> itemBuilder;

  const PaginationListView({
    required this.provider,
    required this.itemBuilder,
    super.key,
  });

  @override
  ConsumerState<PaginationListView> createState() =>
      _PaginationListViewState<T>();
}

class _PaginationListViewState<T extends IModelWithId>
    extends ConsumerState<PaginationListView> {
  final controller = ScrollController();

  @override
  void initState() {
    super.initState();

    controller.addListener(listener);
  }

  void listener() {
    PaginationUtils.paginate(
      controller: controller,
      provider: ref.read(widget.provider.notifier),
    );
  }

  // ... 생략
}


덧붙여서 IModelWithId의 코드도 첨부합니다. 이때 Dart 3의 새로운 Modifier인 interface를 사용했습니다. 이것이 문제가 될 것 같지는 않지만, 혹시 몰라서 알립니다.

 

abstract interface class IModelWithId {
  final String id;

  IModelWithId({
    required this.id,
  });
}
코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

class _PaginationListViewState<T extends IModelWithId> extends ConsumerState<PaginationListView> {

이 부분을

class _PaginationListViewState<T extends IModelWithId> extends ConsumerState<PaginationListView<T>> {

이렇게 해보세요!

limazero14님의 프로필 이미지
limazero14
질문자

typedef PaginationWidgetBuilder<T extends IModelWithId> = Widget Function(
  BuildContext context,
  int index,
  T model,
);

class PaginationListView<T extends IModelWithId>
    extends ConsumerStatefulWidget {
  final StateNotifierProvider<PaginationProvider, CursorPaginationBase>
      provider;
  final PaginationWidgetBuilder<T> itemBuilder;

  const PaginationListView({
    required this.provider,
    required this.itemBuilder,
    super.key,
  });

  @override
  ConsumerState<PaginationListView> createState() =>
      _PaginationListViewState<T>();
}

class _PaginationListViewState<T extends IModelWithId>
    extends ConsumerState<PaginationListView<T>> {
  final controller = ScrollController();

빠른 답변에 감사드립니다. 그러나 여전히 같은 문제가 발생하고 있습니다.

 

위 코드와 같이, ConsumerState<PaginationListView<T>> 를 받게끔 하고 있음에도 여전히 같은 문제가 발생합니다. Dart 그자체의 문제일까요?

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

Dart 자체 문제는 아닐겁니다. 레포지토리 공유해주시면 제가 봐볼게요

limazero14님의 프로필 이미지
limazero14
질문자

직접 봐주신다니 감사합니다! 아래 레포지토리에 해당 강의를 수강하면서 따라한 결과물을 남기고 있습니다: 답변이 해결될 때까지 Public으로 바꿀게요!

 

https://github.com/nx006/delivery_app

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

안녕하세요! 원인을 찾았습니다. itemBuilder의 generic을 제거해주시면 됩니다. 아래 예제 코드 복붙해드립니다.

return PaginationListView<ProductModel>(
  provider: productProvider,
  itemBuilder: (_, index, ProductModel model) => GestureDetector(
    onTap: () {
      context.goNamed(
        RestaurantDetailPage.routeName,
        pathParameters: {
          'rid': model.restaurant.id,
        },
      );
    },
    child: ProductCard.fromProductModel(
      model: model,
    ),
  ),
);

itemBuilder의 generic에 직접 ProductModel을 넣어줄 경우 PaginationListView로부터 T 타입을 내려받는게 아니라 강제로 ProductModel을 새로 넣어주는거라 인터프레터가 서로 연관성이 없다고 보는 것 같습니다!

limazero14님의 프로필 이미지
limazero14
질문자

문제가 해결되었습니다! 감사합니다!

limazero14님의 프로필 이미지
limazero14

작성한 질문수

질문하기