inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

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

20 디렉토리 구조

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

1244

프래프프론트엔드

작성한 질문수 1

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 단에서 잘 호출 하고 있는거 같은데.. 어떻게 해결해야할까요?

 

 

flutter Flutter android ios

답변 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

오준석

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

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 를 찾지를 못하네요.. 무엇이 잘못 되었을까요? 

 

0

오준석

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

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

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

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

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

0

오준석

Provider를 찾지 못하고 있군요.

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

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

MVVM, 클린 아키텍처 관련 질문 있습니다.

0

85

2

가끔씩 ui가 깨지는? 현상이 있어서 질문드립니다.

0

91

1

freezed 3.0 대응된 코드 깃헙에도 업데이트 해주실 수 있으신가요?

0

176

3

sealed class 사용시 기능은 동작하지만 Radio위젯에 선택 표시가 안되는 부분 질문

0

117

2

sealed class 사용시 The getter 'orderType' isn't defined for the type 'NoteOrder<dynamic>' 오류

0

92

2

유즈케이스 관련하여 질문 드립니다.

0

91

1

mockito사용시 오류 해결법

1

123

2

sealed class 사용 문의2

0

75

2

sealed class 사용 문의

0

116

2

freezed3.0에서 build시 when생성되지 않습니다.

0

263

2

Try implementing the missing methods, or make the class abstract. 문제해결 공유

0

236

2

This is likely caused by a misconfigured builder definition. 오류 해결 방법

1

316

3

강의 화면이 안보여요

0

133

3

Flutter에서 추천하는 Navigator, Router

0

322

2

The following ProgressEvent object was thrown resolving an image codec: [object ProgressEvent]

0

324

3

event와 ui_event

0

211

1

코드 색깔 관련 질문

0

207

1

Photo.fleezed.dart,photo.g.dart삭제시 에러

0

203

2

sealed class 적용 시...

0

325

1

클린 아키텍처 질문

0

307

1

sealed class 데이터 접근

0

323

1

서버에서 데이터를 가져와서 사용하는 경우...

0

230

1

뷰/뷰모델 작성 질문

0

269

1

freezed JsonKey 사용 예시 공유

1

789

1