Skip to content

Commit

Permalink
refactor(flutter_login): use RepositoryProvider + dispose (#4365)
Browse files Browse the repository at this point in the history
  • Loading branch information
yerzhant authored and felangel committed Mar 1, 2025
1 parent 6f21d97 commit 1dbc23e
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 36 deletions.
39 changes: 12 additions & 27 deletions examples/flutter_login/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,24 @@ import 'package:flutter_login/login/login.dart';
import 'package:flutter_login/splash/splash.dart';
import 'package:user_repository/user_repository.dart';

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

@override
State<App> createState() => _AppState();
}

class _AppState extends State<App> {
late final AuthenticationRepository _authenticationRepository;
late final UserRepository _userRepository;

@override
void initState() {
super.initState();
_authenticationRepository = AuthenticationRepository();
_userRepository = UserRepository();
}

@override
void dispose() {
_authenticationRepository.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: _authenticationRepository,
return MultiRepositoryProvider(
providers: [
RepositoryProvider(
create: (_) => AuthenticationRepository(),
dispose: (repository) => repository.dispose(),
),
RepositoryProvider(create: (_) => UserRepository()),
],
child: BlocProvider(
lazy: false,
create: (_) => AuthenticationBloc(
authenticationRepository: _authenticationRepository,
userRepository: _userRepository,
create: (context) => AuthenticationBloc(
authenticationRepository: context.read<AuthenticationRepository>(),
userRepository: context.read<UserRepository>(),
)..add(AuthenticationSubscriptionRequested()),
child: const AppView(),
),
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_bloc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 9.1.0

- feat: expose `dispose` callback on `RepositoryProvider` ([#4356](https://github.com/felangel/bloc/pull/4356))

# 9.0.0

- fix: ensure widget is mounted before invoking listener ([#4237](https://github.com/felangel/bloc/pull/4237))
Expand Down
10 changes: 10 additions & 0 deletions packages/flutter_bloc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,16 @@ context.read<RepositoryA>();
RepositoryProvider.of<RepositoryA>(context)
```

Repositories that manage resources which must be disposed can do so via the `dispose` callback:

```dart
RepositoryProvider(
create: (context) => RepositoryA(),
dispose: (context, repository) => repository.close(),
child: ChildA(),
);
```

### MultiRepositoryProvider

**MultiRepositoryProvider** is a Flutter widget that merges multiple `RepositoryProvider` widgets into one.
Expand Down
10 changes: 5 additions & 5 deletions packages/flutter_bloc/lib/src/bloc_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';

/// {@template bloc_provider}
/// Takes a [Create] function that is responsible for
/// Takes a `create` function that is responsible for
/// creating the [Bloc] or [Cubit] and a [child] which will have access
/// to the instance via `BlocProvider.of(context)`.
/// It is used as a dependency injection (DI) widget so that a single instance
Expand All @@ -19,7 +19,7 @@ import 'package:provider/single_child_widget.dart';
/// ```
///
/// It automatically handles closing the instance when used with [Create].
/// By default, [Create] is called only when the instance is accessed.
/// By default, `create` is called only when the instance is accessed.
/// To override this behavior, set [lazy] to `false`.
///
/// ```dart
Expand All @@ -35,7 +35,7 @@ class BlocProvider<T extends StateStreamableSource<Object?>>
extends SingleChildStatelessWidget {
/// {@macro bloc_provider}
const BlocProvider({
required Create<T> create,
required T Function(BuildContext context) create,
Key? key,
this.child,
this.lazy = true,
Expand All @@ -52,7 +52,7 @@ class BlocProvider<T extends StateStreamableSource<Object?>>
///
/// A new [Bloc] or [Cubit] should not be created in `BlocProvider.value`.
/// New instances should always be created using the
/// default constructor within the [Create] function.
/// default constructor within the `create` function.
///
/// ```dart
/// BlocProvider.value(
Expand All @@ -76,7 +76,7 @@ class BlocProvider<T extends StateStreamableSource<Object?>>
/// Defaults to `true`.
final bool lazy;

final Create<T>? _create;
final T Function(BuildContext context)? _create;

final T? _value;

Expand Down
7 changes: 4 additions & 3 deletions packages/flutter_bloc/lib/src/repository_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

/// {@template repository_provider}
/// Takes a [Create] function that is responsible for creating the repository
/// Takes a `create` function that is responsible for creating the repository
/// and a `child` which will have access to the repository via
/// `RepositoryProvider.of(context)`.
/// It is used as a dependency injection (DI) widget so that a single instance
Expand All @@ -28,14 +28,15 @@ import 'package:provider/provider.dart';
class RepositoryProvider<T> extends Provider<T> {
/// {@macro repository_provider}
RepositoryProvider({
required Create<T> create,
required T Function(BuildContext context) create,
void Function(T value)? dispose,
Key? key,
Widget? child,
bool? lazy,
}) : super(
key: key,
create: create,
dispose: (_, __) {},
dispose: (_, value) => dispose?.call(value),
child: child,
lazy: lazy,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_bloc/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_bloc
description: Flutter Widgets that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.
version: 9.0.0
version: 9.1.0
repository: https://github.com/felangel/bloc/tree/master/packages/flutter_bloc
issue_tracker: https://github.com/felangel/bloc/issues
homepage: https://bloclibrary.dev
Expand Down
42 changes: 42 additions & 0 deletions packages/flutter_bloc/test/repository_provider_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -356,5 +356,47 @@ void main() {
final counterText = counterFinder.evaluate().first.widget as Text;
expect(counterText.data, '0');
});

testWidgets('calls dispose callback when disposed', (tester) async {
var disposeCalled = false;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: RepositoryProvider<Repository>(
create: (_) => const Repository(0),
dispose: (repository) {
disposeCalled = true;
expect(repository.data, equals(0));
},
child: Builder(
builder: (context) => Text(
'${context.read<Repository>().data}',
key: const Key('value_data'),
),
),
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => Navigator.of(context).pop(),
child: const Icon(Icons.remove),
),
),
),
),
);
final repositoryFinder = find.byKey(const Key('value_data'));
expect(repositoryFinder, findsOneWidget);

final repositoryText = repositoryFinder.evaluate().first.widget as Text;
expect(repositoryText.data, '0');

final fabFinder = find.byType(FloatingActionButton);
expect(fabFinder, findsOneWidget);

expect(disposeCalled, isFalse);
await tester.tap(fabFinder);
await tester.pumpAndSettle();
expect(disposeCalled, isTrue);
});
});
}

0 comments on commit 1dbc23e

Please sign in to comment.