[flutter] Injectable 클래스를 이용한 플러터 예제 앱 개발 방법

이번 튜토리얼에서는 Injectable 클래스를 사용하여 플러터 예제 앱을 개발하는 방법에 대해 알아보겠습니다. Injectable은 의존성 주입(Dependency Injection)을 구현하기 위한 외부 스냅샷이며, 플러터 앱을 개발할 때 의존성 주입을 효율적으로 관리하는 데 도움을 줍니다.

의존성 주입은 앱의 다른 부분에서 필요로하는 객체를 공급하는 디자인 패턴입니다. 이렇게 하면 의존성을 갖는 클래스를 더 유연하고 유지보수 가능하게 만들 수 있습니다. 필요에 따라 의존성 객체를 쉽게 교체하거나 모의 객체를 사용하여 테스트할 수 있습니다.

시나리오

이 예제에서는 간단한 투두 리스트 앱을 만들 예정입니다. 앱은 두 가지 중요한 클래스로 구성됩니다. 첫 번째는 TodoService 클래스로, 실제로 할 일 목록을 관리하고 조작하는 기능을 제공합니다. 두 번째는 TodoScreen 클래스로, 사용자 인터페이스를 표시하고 사용자의 조작을 처리합니다.

시작하기

먼저, 플러터 프로젝트를 만들고 injectable 패키지를 추가해야 합니다. injectable 패키지는 의존성 주입을 위해 필요한 기능과 어노테이션을 제공합니다. 프로젝트의 pubspec.yaml 파일에 다음과 같이 패키지를 추가하세요:

dependencies:
  injectable: ^1.0.0

그런 다음 패키지를 가져와 get_it을 초기화하고 어떤 객체가 주입되어야 하는지 설정해야 합니다. 프로젝트의 main.dart 파일에서 다음과 같이 설정해봅시다:

import 'package:injectable/injectable.dart';
import 'package:get_it/get_it.dart';

import 'injection.config.dart';

final getIt = GetIt.instance;

void configureDependencies() => $initGetIt(getIt);

위의 코드에서 injection.config.dart 파일은 잠시 후에 자동으로 생성됩니다.

이제 TodoService 클래스를 만들고 의존성 주입을 사용하여 TodoScreen 클래스를 개발해보겠습니다.

abstract class TodoService {
  void add(String todo);

  void remove(int index);

  List<String> getTodos();
}

@LazySingleton(as: TodoService)
class TodoServiceImpl implements TodoService {
  List<String> todos = [];

  @override
  void add(String todo) {
    todos.add(todo);
  }

  @override
  void remove(int index) {
    todos.removeAt(index);
  }

  @override
  List<String> getTodos() {
    return todos;
  }
}

class TodoScreen extends StatelessWidget {
  final TodoService todoService = getIt<TodoService>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Todo List'),
      ),
      body: ListView.builder(
        itemCount: todoService.getTodos().length,
        itemBuilder: (context, index) {
          final todo = todoService.getTodos()[index];
          return ListTile(
            title: Text(todo),
            trailing: IconButton(
              icon: Icon(Icons.delete),
              onPressed: () => todoService.remove(index),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () async {
          final todo = await showDialog<String>(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(),
                actions: [
                  TextButton(
                    child: Text('Cancel'),
                    onPressed: () => Navigator.pop(context),
                  ),
                  ElevatedButton(
                    child: Text('Add'),
                    onPressed: () {
                      final todo = 'New Todo'; // Get value from TextField
                      todoService.add(todo);
                      Navigator.pop(context);
                    },
                  ),
                ],
              );
            },
          );
        },
      ),
    );
  }
}

위의 코드에서 @LazySingleton(as: TodoService) 어노테이션을 사용하여 TodoServiceImpl 클래스가 TodoService 인터페이스에 주입될 수 있도록 설정했습니다. 이렇게 하면 TodoServiceImpl에 대한 인스턴스를 얻을 수 있으며, 해당 인스턴스는 여러 클래스에서 공유됩니다.

마지막으로, main.dart 파일에서 configureDependencies() 함수를 호출하고 TodoScreen 위젯을 루트 위젯으로 설정해봅시다:

void main() {
  configureDependencies();
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Todo App',
      home: TodoScreen(),
    );
  }
}

테스트하기

의존성 주입을 사용하여 개발한 앱을 테스트하는 것도 매우 쉽습니다. TodoService의 구현체를 모의 객체로 대체하거나 테스트용으로 작성된 클래스로 변경하여 테스트할 수 있습니다.

예를 들어, TodoService의 목업(mock) 클래스를 작성해보겠습니다:

class MockTodoService implements TodoService {
  final List<String> todos = ['Mock Todo 1', 'Mock Todo 2'];

  @override
  void add(String todo) {
    todos.add(todo);
  }

  @override
  void remove(int index) {
    todos.removeAt(index);
  }

  @override
  List<String> getTodos() {
    return todos;
  }
}

void main() {
  configureDependencies();

  // MockTodoService로 설정하기
  getIt.registerLazySingleton<TodoService>(() => MockTodoService());

  testWidgets('TodoScreen 테스트', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());

    // TodoScreen의 테스트 로직 구현
  });
}

위의 코드에서는 getIt.registerLazySingleton<TodoService>(() => MockTodoService())를 사용하여 TodoService의 인스턴스를 MockTodoService로 설정합니다. 이렇게 하면 테스트 중에 TodoService에 대한 모의 객체를 사용할 수 있습니다. 테스트 로직은 TodoScreen을 위젯을 사용하여 작성하면 됩니다.

결론

이번 튜토리얼에서는 Injectable 클래스를 사용하여 플러터 예제 앱을 개발하는 방법에 대해 알아보았습니다. 의존성 주입을 사용하면 앱의 유지보수성과 테스트 용이성이 크게 향상됩니다. 플러터를 사용하여 앱을 개발할 때는 Injectable을 사용하여 의존성 주입을 관리하는 것이 좋습니다.

Injectable과 함께 플러터 앱을 개발해보세요! 세련된 구조와 효율적인 코드 관리를 경험하실 수 있을 것입니다.

References