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

프래프프론트엔드님의 프로필 이미지
프래프프론트엔드

작성한 질문수

Flutter 중급 1편 - 클린 아키텍처

20 디렉토리 구조

의존성 주입 질문있습니다.

작성

·

1.1K

·

수정됨

0

안녕하세요~ 덕분에 클린 아키텍쳐 구조 잘 공부했습니다.

혹시 의존성 주입부분에서 질문이 있습니다.

제가 개인적으로 연습을 하면서 클린 아키텍쳐를 적용하고 있습니다.

  1. /di/provider_setup.dart 에서 한번에 의존성 주입

List<ChangeNotifierProvider> getProviders() {
  final dio = Dio();

  SongRepository repository = SongRepository(dio);

  UseCases useCases = UseCases(
    getSearchSong: GetSearchSongUseCase(repository: repository),
    getSearchSinger: GetSearchSingerUseCase(repository: repository),
    getRecentlySongsList: GetRecentlySongsListUseCase(repository:repository),
  );

  SearchViewModel searchViewModel = SearchViewModel(useCases: useCases);
  HomeViewModel homeViewModel = HomeViewModel(useCases: useCases);

  return [
    ChangeNotifierProvider(create: (_) => searchViewModel),
    ChangeNotifierProvider(create: (_) => homeViewModel),
  ];
}
  1. main 에서 주입

void main() {
  // provider 호출
  final providers = getProviders();

  runApp(
    MultiProvider(
      providers: providers,
      child: const MyApp(),
    ),
  );
}
  1. context.watch<SearchViewModel>(); 은 잘 작동해서 뷰에 출력을 잘 하고있습니다.

    class _SearchScreenState extends State<SearchScreen> {
    
      @override
      Widget build(BuildContext context) {
        final searchViewModel = context.watch<SearchViewModel>();
        final state = searchViewModel.state;
        ...
    }
  2. context.watch<HomeViewModel>();은 에러가 발생합니다.

    class _Body extends StatelessWidget {
      const _Body({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final homeViewModel = context.watch<HomeViewModel>();
        final state = homeViewModel.state;
        ...
    }

에러내용

======== Exception caught by widgets library =======================================================
The following ProviderNotFoundException was thrown building _Body(dirty):
Error: Could not find the correct Provider<HomeViewModel> above this _Body Widget

This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:

- You added a new provider in your `main.dart` and performed a hot-reload.
  To fix, perform a hot-restart.

- The provider you are trying to read is in a different route.

  Providers are "scoped". So if you insert of provider inside a route, then
  other routes will not be able to access that provider.

- You used a `BuildContext` that is an ancestor of the provider you are trying to read.

  Make sure that _Body is under your MultiProvider/Provider<HomeViewModel>.
  This usually happens when you are creating a provider and trying to read it immediately.

  For example, instead of:

  ```
  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // Will throw a ProviderNotFoundError, because `context` is associated
      // to the widget that is the parent of `Provider<Example>`
      child: Text(context.watch<Example>().toString()),
    );
  }
  ```

  consider using `builder` like so:

  ```
  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // we use `builder` to obtain a new `BuildContext` that has access to the provider
      builder: (context, child) {
        // No longer throws
        return Text(context.watch<Example>().toString());
      }
    );
  }
  ```

If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter

The relevant error-causing widget was: 
When the exception was thrown, this was the stack: 
#0      Provider._inheritedElementOf (package:provider/src/provider.dart:343:7)
#1      Provider.of (package:provider/src/provider.dart:293:30)
#2      WatchContext.watch (package:provider/src/provider.dart:693:21)
#3      _Body.build (package:what_do_you_want_to_sing/presentation/home/home_screen.dart:79:35)
#4      StatelessElement.build (package:flutter/src/widgets/framework.dart:4949:49)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4878:15)
#6      Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
#7      BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2667:19)
#8      WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:882:21)
#9      RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:378:5)
#10     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
#11     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1104:9)
#12     SchedulerBinding.scheduleWarmUpFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:881:7)
(elided 4 frames from class _RawReceivePortImpl, class _Timer, and dart:async-patch)

저번에 이런 에러가 발생했을때, 의존성 주입이 안된 상태에서 context.watch() 를 해서 오류가 나 의존성을 추가해 해결했습니다.

하지만 이번에는 잘모르겠습니다. 의존성도 잘 주입되어서 view 단에서 잘 호출 하고 있는거 같은데.. 어떻게 해결해야할까요?

 

 

답변 3

0

해결했습니다! Provider 못찾는 문제라고 말씀주셔서

Provider 생성및 선언해주는 부분을 잘 찾아서 해보니

getProviders() 함수의 리턴 해주는 배열에 각각 타입을 넣어주니 호출하는 부분에서 Provider 를 못찾는다는 에러가 해결되었습니다.

타입을 안넣어주니 각 Provider 를 못찾는 것이였습니다.

List<ChangeNotifierProvider> getProviders() {
  final dio = Dio();

  SongRepository repository = SongRepository(dio);

  UseCases useCases = UseCases(
    getSearchSong: GetSearchSongUseCase(repository: repository),
    getSearchSinger: GetSearchSingerUseCase(repository: repository),
    getRecentlySongsList: GetRecentlySongsListUseCase(repository: repository),
  );

  SearchViewModel searchViewModel = SearchViewModel(useCases: useCases);
  HomeViewModel homeViewModel = HomeViewModel(useCases: useCases);

  return [
// 타입추가
    ChangeNotifierProvider<SearchViewModel>(create: (_) => searchViewModel),
// 타입추가
    ChangeNotifierProvider<HomeViewModel>(create: (_) => homeViewModel),
  ];
}
오준석님의 프로필 이미지
오준석
지식공유자

해결하셨다니 다행입니다.

0

답변 주셔서 감사합니다.

제가 선생님 강의를 공부 하면서 적용한 방법은

provider_setup.dart 에서 의존성 선언 및 생성해주고

 main.dart 에서 MultiProvider 로 Provider 여러개 주입시켜주고

home_screen.dart 에서는 주입시켜준 Provider를 찾아서 상태에 접근한다. 라고 생각하며 코드를 작성했습니다!

main.dart 입니다.

void main() {
  // provider 호출
  final providers = getProviders();

  runApp(
    MultiProvider(
      providers: providers,
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'title',
      home: HomeScreen(),
    );
  }
}

homeScreen 입니다.

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DefaultLayout(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(context,
              MaterialPageRoute(builder: (context) => const SearchScreen()));
        },
        backgroundColor: Colors.black,
        child: const Icon(Icons.search_rounded),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: const [
              _Header(),
              SizedBox(height: 8.0),
	      _Body(),
              SizedBox(height: 8.0),
            ],
        ),
      ),
    );
  }
}
// _Header 코드

// _Body 코드 
class _Body extends StatelessWidget {

  const _Body({Key? key}) : super(key: key);

  @override

  Widget build(BuildContext context) {

    final homeViewModel = context.watch<HomeViewModel>();

    final state = homeViewModel.state;

    return ListView.builder(

      itemBuilder: (BuildContext context, int index) {

        final data = state.recentlyList[index];

        return ListCard(data: data);

      },

    );
}

 

 

검색 페이지에는 이미 Provider 를 주입해서 잘 출력하고있습니다.

하지만 homeScreen 클래스에서는 Provider 를 찾지를 못하네요.. 무엇이 잘못 되었을까요? 

 

오준석님의 프로필 이미지
오준석
지식공유자

context 가 중첩되서 그렇습니다. 대표적인 안티패턴 코드인데요.

아마 Builder 위젯으로 감싸면 될 건데 오히려 코드가 복잡해 집니다.

_Body StatelessWidget 대신 Widget을 리턴하는 일반 헬퍼 함수로 변경하시거나,

_Body 는 ViewModel에 접근하지 않고 생성자를 통해서 homeViewModel.state 를 전달받도록 수정하시면 됩니다.

HomeViewModel 은 HomeScreen 에서만 접근하도록이요.

0

오준석님의 프로필 이미지
오준석
지식공유자

Provider를 찾지 못하고 있군요.

_Body 까지의 위젯 Tree 가 어떻게 되실까요?

일반적인 구조라면 문제가 없어야 하는데, 혹시 좀 특이한 구조가 있거나 하진 않을까요?

프래프프론트엔드님의 프로필 이미지
프래프프론트엔드

작성한 질문수

질문하기