Flutter Riverpod Tutorial Part 8: Dependency injection

Flutter Riverpod Tutorial Part 8: Dependency injection

In this tutorial, we'll explore how to set up dependency injection with Riverpod, manage dependencies across your app, and test with dependency injection.

Dependency injection is a technique where an object receives its dependencies from an external source rather than creating them itself, promoting loose coupling and easier testing.

Sections Covered:

  1. Setting up Dependency Injection with Riverpod

  2. Managing Dependencies Across the App

  3. Testing with Dependency Injection

1. Setting up Dependency Injection with Riverpod

Step 1: Define Dependencies

Let's create a service class that you'll use as a dependency. For example we will use this AuthService

class AuthService {
  String login(String login, String password) {
    //Mock Login Service
    if (login == 'admin' && password == 'admin') {
      return 'Login successful!';
    } else {
      return 'Login failed!';
    }
  }
}

Step 2: Create a Provider for the Service

Since we are going to test the code we need to create the provider in a separate file. like auth_service_provider.dart.

final authServiceProvider = Provider<AuthService>((ref) => AuthService());

2. Managing Dependencies Across the App

So in here let's use the authServiceProvider in our LoginPage

class LoginPage extends HookConsumerWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authService = ref.watch(authServiceProvider);
    final usernameController = useTextEditingController();
    final passwordController = useTextEditingController();
    final message = useState('');

    return Scaffold(
      appBar: AppBar(
        title: const Text('Login'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              controller: usernameController,
              decoration: const InputDecoration(labelText: 'Username'),
            ),
            const SizedBox(
              height: 20,
            ),
            TextField(
              controller: passwordController,
              decoration: const InputDecoration(labelText: 'Password'),
            ),
            const SizedBox(
              height: 20,
            ),
            ElevatedButton(
                onPressed: () {
                  final result = authService.login(
                      usernameController.text, passwordController.text);
                  message.value = result;
                },
                child: const Text('Login')),
            const SizedBox(
              height: 20,
            ),
            Text(
              message.value,
              style: const TextStyle(fontSize: 24),
            ),
          ],
        ),
      ),
    );
  }
}

3. Testing with Dependency Injection

Step 1: Write Tests for the AuthService

Create a test file, auth_service_test.dart. Make sure to put this under the test/ directory.

import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/auth_provider.dart'; // Adjust the import as per your project structure
import 'package:hooks_riverpod/hooks_riverpod.dart';

void main() {
  test('AuthService login test', () {
    final container = ProviderContainer();
    final authService = container.read(authServiceProvider);

    expect(authService.login('admin', 'admin'), 'Login successful!');
    expect(authService.login('user', 'wrong_password'), 'Login failed!');
  });
}

So ProviderContainer() assists us with managing Providers and overriding them if needed. We don't need to do it in our regular files because ProviderScope() we using in main.dart already encompasses our entire application.

Step 2: Mock Dependencies for Testing

Create a mock version of AuthService for more complex testing scenarios.

import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/auth_provider.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class MockAuthService extends AuthService {
  @override
  String login(String username, String password) {
    return 'Mock login for $username';
  }
}

void main() {
  test('AuthService mock test', () {
    final container = ProviderContainer(
      overrides: [
        authServiceProvider.overrideWithValue(MockAuthService()),
      ],
    );
    final authService = container.read(authServiceProvider);

    expect(authService.login('test', 'test'), 'Mock login for test');
  });
}

Perfect Now. If we want to execute the test. Just run the following command

flutter test

Summary:

In this tutorial, we explored how to set up dependency injection with Riverpod, manage dependencies across your app, and write tests using dependency injection. These techniques help create modular, testable, and maintainable applications.

Did you find this article valuable?

Support Harish Kunchala by becoming a sponsor. Any amount is appreciated!