diff --git a/complete_example/analysis_options.yaml b/complete_example/analysis_options.yaml index 61b6c4d..e4675f0 100644 --- a/complete_example/analysis_options.yaml +++ b/complete_example/analysis_options.yaml @@ -1,29 +1,8 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml +analyzer: + errors: + invalid_annotation_target: ignore + linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/complete_example/lib/auth.dart b/complete_example/lib/auth.dart index 5393813..6e1ad03 100644 --- a/complete_example/lib/auth.dart +++ b/complete_example/lib/auth.dart @@ -1,108 +1,111 @@ import 'dart:async'; import 'dart:math'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class User { - const User({ - required this.displayName, - required this.email, - required this.token, - }); +import 'user.dart'; - final String displayName; - final String email; - final String token; -} +part 'auth.g.dart'; -// TODO in the next version: Use Freezed to generate states. -typedef AsyncUser = AsyncValue; +/// A mock of an Authenticated User +const _dummyUser = User.signedIn( + id: 0, + displayName: "My Name", + email: "My Email", + token: "some-updated-secret-auth-token", +); -final authProvider = AsyncNotifierProvider(() { - return AuthNotifier(); -}); +@riverpod +class AuthNotifier extends _$AuthNotifier { + late SharedPreferences sharedPreferences; + static const _sharedPrefsKey = 'token'; -class AuthNotifier extends AsyncNotifier { - AuthNotifier(); - static const _key = 'token'; + /// Mock of the duration of a network request + final _networkRoundTripTime = Random().nextInt(750); @override - FutureOr build() async { - final sharedPreferences = await SharedPreferences.getInstance(); + FutureOr build() async { + sharedPreferences = await SharedPreferences.getInstance(); - ref.listenSelf((_, next) { - final val = next.valueOrNull; - if (val == null) { - sharedPreferences.remove(_key); - return; - } - sharedPreferences.setString(_key, val.email); - }); + _persistenceRefreshLogic(); + return await _loginRecoveryAttempt(); + } + + /// Tries to perform a login with the saved token on the persistant storage. + /// If _anything_ goes wrong, deletes the internal token and returns a [User.signedOut]. + Future _loginRecoveryAttempt() async { try { - // This operation might fail for... reasons - final savedToken = sharedPreferences.getString(_key); - if (savedToken == null) return null; + final savedToken = sharedPreferences.getString(_sharedPrefsKey); + if (savedToken == null) { + throw const UnauthorizedException( + "Couldn't find the authentication token"); + } - // This request might also fail return await _loginWithToken(savedToken); - } catch (error, stackTrace) { - // If anything goes wrong, give a non-authenticated outcome - await sharedPreferences.remove(_key); - print(error); - print(stackTrace); - return null; + } catch (_, __) { + await sharedPreferences.remove(_sharedPrefsKey); + return const User.signedOut(); } } - Future _loginWithToken(String token) async { - // here the token should be used to perform a login request + /// Mock of a request performed on logout (might be common, or not, whatevs). + Future logout() async { + await Future.delayed(Duration(milliseconds: _networkRoundTripTime)); + state = const AsyncValue.data(User.signedOut()); + } + + /// Mock of a successful login attempt, which results come from the network. + Future login(String email, String password) async { + state = await AsyncValue.guard(() async { + return Future.delayed( + Duration(milliseconds: _networkRoundTripTime), + () => _dummyUser, + ); + }); + } + + /// Mock of a login request performed with a saved token. + /// If such request fails, this method will throw an [UnauthorizedException]. + Future _loginWithToken(String token) async { final logInAttempt = await Future.delayed( - const Duration(milliseconds: 750), - () => Random().nextBool(), // mock + Duration(milliseconds: _networkRoundTripTime), + () => true, ); - // If the attempts succeeds, return the result out - if (logInAttempt) { - return const User( - displayName: "My Name", - email: "My Email", - token: "some-updated-secret-auth-token", - ); - } + if (logInAttempt) return _dummyUser; - // If the attempt fails, or returns 401, or whatever, this should fail. throw const UnauthorizedException('401 Unauthorized or something'); } - Future logout() async { - final sharedPreferences = await SharedPreferences.getInstance(); + /// Internal method used to listen authentication state changes. + /// When the auth object is in a loading state, nothing happens. + /// When the auth object is in a error state, we choose to remove the token + /// Otherwise, we expect the current auth value to be reflected in our persitence API + void _persistenceRefreshLogic() { + ref.listenSelf((_, next) { + if (next.isLoading) return; + if (next.hasError) { + sharedPreferences.remove(_sharedPrefsKey); + return; + } - // Remove the token from persistence, first - await sharedPreferences.remove(_key); - // No request is mocked here but I guess we could: logout - state = const AsyncUser.data(null); - } + final val = next.requireValue; - Future login(String email, String password) async { - // Simple mock of a successful login attempt - state = await AsyncUser.guard(() async { - return Future.delayed( - Duration(milliseconds: Random().nextInt(750)), - () => const User( - displayName: "My Name", - email: "My Email", - token: 'someToken', - ), + val.map( + signedIn: (signedIn) { + sharedPreferences.setString(_sharedPrefsKey, signedIn.token); + }, + signedOut: (signedOut) { + sharedPreferences.remove(_sharedPrefsKey); + }, ); }); } - - bool get isAuthenticated => state.valueOrNull != null; - bool get isLoading => state.isLoading; } +/// Simple mock of a 401 exception class UnauthorizedException implements Exception { final String message; const UnauthorizedException(this.message); diff --git a/complete_example/lib/auth.g.dart b/complete_example/lib/auth.g.dart new file mode 100644 index 0000000..5ce951a --- /dev/null +++ b/complete_example/lib/auth.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +String $AuthNotifierHash() => r'587998753a879e6c3f8912f3913c0e2ea5eb1b14'; + +/// See also [AuthNotifier]. +final authNotifierProvider = + AutoDisposeAsyncNotifierProvider( + AuthNotifier.new, + name: r'authNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : $AuthNotifierHash, +); +typedef AuthNotifierRef = AutoDisposeAsyncNotifierProviderRef; + +abstract class _$AuthNotifier extends AutoDisposeAsyncNotifier { + @override + FutureOr build(); +} diff --git a/complete_example/lib/main.dart b/complete_example/lib/main.dart index 7151e6c..a4d5157 100644 --- a/complete_example/lib/main.dart +++ b/complete_example/lib/main.dart @@ -16,35 +16,20 @@ class MyAwesomeApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final asyncRouter = ref.watch(routerProvider); + final router = ref.watch(routerProvider); - if (asyncRouter.hasValue) { - final router = asyncRouter.requireValue; - return MaterialApp.router( - routeInformationParser: router.routeInformationParser, - routerDelegate: router.routerDelegate, - routeInformationProvider: router.routeInformationProvider, - title: 'flutter_riverpod + go_router Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - ); - } - - return asyncRouter.maybeWhen( - error: (error, stackTrace) { - print(error); - return const Text("Something went very, very wrong."); - }, - orElse: () => const CircularProgressIndicator(), + return MaterialApp.router( + routerConfig: router, + title: 'flutter_riverpod + go_router Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), ); } } class HomePage extends ConsumerWidget { const HomePage({super.key}); - static String get routeName => 'home'; - static String get routeLocation => '/'; @override Widget build(BuildContext context, WidgetRef ref) { @@ -58,7 +43,7 @@ class HomePage extends ConsumerWidget { const Text("Home Page"), ElevatedButton( onPressed: () { - ref.read(authProvider.notifier).logout(); + ref.read(authNotifierProvider.notifier).logout(); }, child: const Text("Logout"), ), @@ -71,8 +56,6 @@ class HomePage extends ConsumerWidget { class LoginPage extends ConsumerWidget { const LoginPage({super.key}); - static String get routeName => 'login'; - static String get routeLocation => '/$routeName'; @override Widget build(BuildContext context, WidgetRef ref) { @@ -86,7 +69,7 @@ class LoginPage extends ConsumerWidget { const Text("Login Page"), ElevatedButton( onPressed: () async { - ref.read(authProvider.notifier).login( + ref.read(authNotifierProvider.notifier).login( "myEmail", "myPassword", ); @@ -102,8 +85,6 @@ class LoginPage extends ConsumerWidget { class SplashPage extends StatelessWidget { const SplashPage({super.key}); - static String get routeName => 'splash'; - static String get routeLocation => '/$routeName'; @override Widget build(BuildContext context) { diff --git a/complete_example/lib/riverpod_logger.dart b/complete_example/lib/riverpod_logger.dart index c22c099..eba129e 100644 --- a/complete_example/lib/riverpod_logger.dart +++ b/complete_example/lib/riverpod_logger.dart @@ -9,11 +9,12 @@ class RiverpodLogger extends ProviderObserver { Object? newValue, ProviderContainer container, ) { + // TODO use a proper logger print(''' { provider: ${provider.name ?? provider.runtimeType}, - oldValue: ${previousValue ?? 'None'}, - newValue: ${newValue ?? 'None'} + oldValue: $previousValue, + newValue: $newValue } '''); super.didUpdateProvider(provider, previousValue, newValue, container); diff --git a/complete_example/lib/router.dart b/complete_example/lib/router.dart index cf8c0a1..0bb796b 100644 --- a/complete_example/lib/router.dart +++ b/complete_example/lib/router.dart @@ -1,57 +1,25 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'auth.dart'; -import 'main.dart'; +import 'router_notifier.dart'; +import 'routes.dart'; -final _key = GlobalKey(); +part 'router.g.dart'; -final routerProvider = FutureProvider((ref) async { - final isAuth = await ref.watch( - authProvider.selectAsync((value) => value != null), - ); +final _key = GlobalKey(debugLabel: 'routerKey'); + +@riverpod +GoRouter router(RouterRef ref) { + ref.watch(routerNotifierProvider); + final notifier = ref.read(routerNotifierProvider.notifier); return GoRouter( navigatorKey: _key, + refreshListenable: notifier, debugLogDiagnostics: true, - initialLocation: SplashPage.routeLocation, - routes: [ - GoRoute( - path: SplashPage.routeLocation, - name: SplashPage.routeName, - builder: (context, state) { - return const SplashPage(); - }, - ), - GoRoute( - path: HomePage.routeLocation, - name: HomePage.routeName, - builder: (context, state) { - return const HomePage(); - }, - ), - GoRoute( - path: LoginPage.routeLocation, - name: LoginPage.routeName, - builder: (context, state) { - return const LoginPage(); - }, - ), - ], - redirect: (context, state) { - // If our async state is loading, don't perform redirects, yet - if (isAuth == null) return null; - - final isSplash = state.location == SplashPage.routeLocation; - if (isSplash) { - return isAuth ? HomePage.routeLocation : LoginPage.routeLocation; - } - - final isLoggingIn = state.location == LoginPage.routeLocation; - if (isLoggingIn) return isAuth ? HomePage.routeLocation : null; - - return isAuth ? null : SplashPage.routeLocation; - }, + initialLocation: SplashRoute.path, + routes: notifier.routes, + redirect: notifier.redirect, ); -}); +} diff --git a/complete_example/lib/router.g.dart b/complete_example/lib/router.g.dart new file mode 100644 index 0000000..8d14366 --- /dev/null +++ b/complete_example/lib/router.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'router.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +String $routerHash() => r'f928f86033f77a05a3a9258abeb50e25d044c1a5'; + +/// See also [router]. +final routerProvider = AutoDisposeProvider( + router, + name: r'routerProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : $routerHash, +); +typedef RouterRef = AutoDisposeProviderRef; diff --git a/complete_example/lib/router_notifier.dart b/complete_example/lib/router_notifier.dart new file mode 100644 index 0000000..abb14cd --- /dev/null +++ b/complete_example/lib/router_notifier.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'auth.dart'; +import 'routes.dart'; + +part 'router_notifier.g.dart'; + +@riverpod +class RouterNotifier extends _$RouterNotifier implements Listenable { + VoidCallback? routerListener; + bool? isAuth; + + @override + void build() { + ref.listen( + authNotifierProvider.select((value) => value), + (previous, next) { + if (next.isLoading) return; + + isAuth = next.requireValue.map( + signedIn: (_) => true, + signedOut: (_) => false, + ); + + routerListener?.call(); + }, + ); + } + + String? redirect(BuildContext context, GoRouterState state) { + if (isAuth == null) return null; + final isSplash = state.location == SplashRoute.path; + + if (isSplash) { + return isAuth! ? HomeRoute.path : LoginRoute.path; + } + + final isLoggingIn = state.location == LoginRoute.path; + if (isLoggingIn) return isAuth! ? HomeRoute.path : null; + + return isAuth! ? null : SplashRoute.path; + } + + /// Our application routes. Obtained through code generation + List get routes => $appRoutes; + + /// Adds [GoRouter]'s listener as specified by its [Listenable]. + /// [GoRouteInformationProvider] uses this method on creation to handle its + /// internal [ChangeNotifier]. + /// Check out the internal implementation of [GoRouter] and + /// [GoRouteInformationProvider] to see this in action. + @override + void addListener(VoidCallback listener) { + routerListener = listener; + } + + /// Removes [GoRouter]'s listener as specified by its [Listenable]. + /// [GoRouteInformationProvider] uses this method when disposing, + /// so that it removes its callback when destroyed. + /// Check out the internal implementation of [GoRouter] and + /// [GoRouteInformationProvider] to see this in action. + @override + void removeListener(VoidCallback listener) { + routerListener = null; + } +} diff --git a/complete_example/lib/router_notifier.g.dart b/complete_example/lib/router_notifier.g.dart new file mode 100644 index 0000000..7429cf0 --- /dev/null +++ b/complete_example/lib/router_notifier.g.dart @@ -0,0 +1,48 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'router_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +String $RouterNotifierHash() => r'97f2e52207151ab82f78803b85c3016d6c7a90e9'; + +/// See also [RouterNotifier]. +final routerNotifierProvider = + AutoDisposeNotifierProvider( + RouterNotifier.new, + name: r'routerNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $RouterNotifierHash, +); +typedef RouterNotifierRef = AutoDisposeNotifierProviderRef; + +abstract class _$RouterNotifier extends AutoDisposeNotifier { + @override + void build(); +} diff --git a/complete_example/lib/routes.dart b/complete_example/lib/routes.dart new file mode 100644 index 0000000..a9b62bd --- /dev/null +++ b/complete_example/lib/routes.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'main.dart'; + +part 'routes.g.dart'; + +@TypedGoRoute(path: SplashRoute.path) +class SplashRoute extends GoRouteData { + const SplashRoute(); + static const path = '/splash'; + + @override + Widget build(BuildContext context) { + return const SplashPage(); + } +} + +@TypedGoRoute(path: HomeRoute.path) +class HomeRoute extends GoRouteData { + const HomeRoute(); + static const path = '/home'; + + @override + Widget build(BuildContext context) { + return const HomePage(); + } +} + +@TypedGoRoute(path: LoginRoute.path) +class LoginRoute extends GoRouteData { + const LoginRoute(); + static const path = '/login'; + + @override + Widget build(BuildContext context) { + return const LoginPage(); + } +} diff --git a/complete_example/lib/routes.g.dart b/complete_example/lib/routes.g.dart new file mode 100644 index 0000000..a024469 --- /dev/null +++ b/complete_example/lib/routes.g.dart @@ -0,0 +1,64 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'routes.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $splashRoute, + $homeRoute, + $loginRoute, + ]; + +GoRoute get $splashRoute => GoRouteData.$route( + path: '/splash', + factory: $SplashRouteExtension._fromState, + ); + +extension $SplashRouteExtension on SplashRoute { + static SplashRoute _fromState(GoRouterState state) => const SplashRoute(); + + String get location => GoRouteData.$location( + '/splash', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +GoRoute get $homeRoute => GoRouteData.$route( + path: '/home', + factory: $HomeRouteExtension._fromState, + ); + +extension $HomeRouteExtension on HomeRoute { + static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + + String get location => GoRouteData.$location( + '/home', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +GoRoute get $loginRoute => GoRouteData.$route( + path: '/login', + factory: $LoginRouteExtension._fromState, + ); + +extension $LoginRouteExtension on LoginRoute { + static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); + + String get location => GoRouteData.$location( + '/login', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} diff --git a/complete_example/lib/user.dart b/complete_example/lib/user.dart new file mode 100644 index 0000000..2abfa50 --- /dev/null +++ b/complete_example/lib/user.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user.freezed.dart'; + +@freezed +class User with _$User { + const factory User.signedIn({ + required int id, + required String displayName, + required String email, + required String token, + }) = SignedIn; + + const factory User.signedOut() = SignedOut; +} diff --git a/complete_example/lib/user.freezed.dart b/complete_example/lib/user.freezed.dart new file mode 100644 index 0000000..72431bd --- /dev/null +++ b/complete_example/lib/user.freezed.dart @@ -0,0 +1,356 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'user.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$User { + @optionalTypeArgs + TResult when({ + required TResult Function( + int id, String displayName, String email, String token) + signedIn, + required TResult Function() signedOut, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int id, String displayName, String email, String token)? + signedIn, + TResult? Function()? signedOut, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int id, String displayName, String email, String token)? + signedIn, + TResult Function()? signedOut, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(SignedIn value) signedIn, + required TResult Function(SignedOut value) signedOut, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SignedIn value)? signedIn, + TResult? Function(SignedOut value)? signedOut, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SignedIn value)? signedIn, + TResult Function(SignedOut value)? signedOut, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserCopyWith<$Res> { + factory $UserCopyWith(User value, $Res Function(User) then) = + _$UserCopyWithImpl<$Res, User>; +} + +/// @nodoc +class _$UserCopyWithImpl<$Res, $Val extends User> + implements $UserCopyWith<$Res> { + _$UserCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$SignedInCopyWith<$Res> { + factory _$$SignedInCopyWith( + _$SignedIn value, $Res Function(_$SignedIn) then) = + __$$SignedInCopyWithImpl<$Res>; + @useResult + $Res call({int id, String displayName, String email, String token}); +} + +/// @nodoc +class __$$SignedInCopyWithImpl<$Res> + extends _$UserCopyWithImpl<$Res, _$SignedIn> + implements _$$SignedInCopyWith<$Res> { + __$$SignedInCopyWithImpl(_$SignedIn _value, $Res Function(_$SignedIn) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? displayName = null, + Object? email = null, + Object? token = null, + }) { + return _then(_$SignedIn( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + displayName: null == displayName + ? _value.displayName + : displayName // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$SignedIn implements SignedIn { + const _$SignedIn( + {required this.id, + required this.displayName, + required this.email, + required this.token}); + + @override + final int id; + @override + final String displayName; + @override + final String email; + @override + final String token; + + @override + String toString() { + return 'User.signedIn(id: $id, displayName: $displayName, email: $email, token: $token)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SignedIn && + (identical(other.id, id) || other.id == id) && + (identical(other.displayName, displayName) || + other.displayName == displayName) && + (identical(other.email, email) || other.email == email) && + (identical(other.token, token) || other.token == token)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, displayName, email, token); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SignedInCopyWith<_$SignedIn> get copyWith => + __$$SignedInCopyWithImpl<_$SignedIn>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + int id, String displayName, String email, String token) + signedIn, + required TResult Function() signedOut, + }) { + return signedIn(id, displayName, email, token); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int id, String displayName, String email, String token)? + signedIn, + TResult? Function()? signedOut, + }) { + return signedIn?.call(id, displayName, email, token); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int id, String displayName, String email, String token)? + signedIn, + TResult Function()? signedOut, + required TResult orElse(), + }) { + if (signedIn != null) { + return signedIn(id, displayName, email, token); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SignedIn value) signedIn, + required TResult Function(SignedOut value) signedOut, + }) { + return signedIn(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SignedIn value)? signedIn, + TResult? Function(SignedOut value)? signedOut, + }) { + return signedIn?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SignedIn value)? signedIn, + TResult Function(SignedOut value)? signedOut, + required TResult orElse(), + }) { + if (signedIn != null) { + return signedIn(this); + } + return orElse(); + } +} + +abstract class SignedIn implements User { + const factory SignedIn( + {required final int id, + required final String displayName, + required final String email, + required final String token}) = _$SignedIn; + + int get id; + String get displayName; + String get email; + String get token; + @JsonKey(ignore: true) + _$$SignedInCopyWith<_$SignedIn> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SignedOutCopyWith<$Res> { + factory _$$SignedOutCopyWith( + _$SignedOut value, $Res Function(_$SignedOut) then) = + __$$SignedOutCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SignedOutCopyWithImpl<$Res> + extends _$UserCopyWithImpl<$Res, _$SignedOut> + implements _$$SignedOutCopyWith<$Res> { + __$$SignedOutCopyWithImpl( + _$SignedOut _value, $Res Function(_$SignedOut) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$SignedOut implements SignedOut { + const _$SignedOut(); + + @override + String toString() { + return 'User.signedOut()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SignedOut); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + int id, String displayName, String email, String token) + signedIn, + required TResult Function() signedOut, + }) { + return signedOut(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int id, String displayName, String email, String token)? + signedIn, + TResult? Function()? signedOut, + }) { + return signedOut?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int id, String displayName, String email, String token)? + signedIn, + TResult Function()? signedOut, + required TResult orElse(), + }) { + if (signedOut != null) { + return signedOut(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SignedIn value) signedIn, + required TResult Function(SignedOut value) signedOut, + }) { + return signedOut(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SignedIn value)? signedIn, + TResult? Function(SignedOut value)? signedOut, + }) { + return signedOut?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SignedIn value)? signedIn, + TResult Function(SignedOut value)? signedOut, + required TResult orElse(), + }) { + if (signedOut != null) { + return signedOut(this); + } + return orElse(); + } +} + +abstract class SignedOut implements User { + const factory SignedOut() = _$SignedOut; +} diff --git a/complete_example/pubspec.lock b/complete_example/pubspec.lock index 76e6fd5..bcc31fb 100644 --- a/complete_example/pubspec.lock +++ b/complete_example/pubspec.lock @@ -1,6 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "50.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" async: dependency: transitive description: @@ -15,6 +36,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.7" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.4.2" characters: dependency: transitive description: @@ -22,6 +99,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" clock: dependency: transitive description: @@ -29,6 +113,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" collection: dependency: transitive description: @@ -36,6 +127,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.4" fake_async: dependency: transitive description: @@ -57,6 +169,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -86,13 +205,76 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" go_router: dependency: "direct main" description: name: go_router url: "https://pub.dartlang.org" source: hosted - version: "5.0.5" + version: "5.1.9" + go_router_builder: + dependency: "direct dev" + description: + name: go_router_builder + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.15" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" js: dependency: transitive description: @@ -100,6 +282,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "6.5.4" lints: dependency: transitive description: @@ -135,6 +331,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -163,6 +373,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + path_to_regexp: + dependency: transitive + description: + name: path_to_regexp + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" platform: dependency: transitive description: @@ -177,6 +394,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" process: dependency: transitive description: @@ -184,6 +408,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" riverpod: dependency: transitive description: @@ -191,6 +429,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" shared_preferences: dependency: "direct main" description: @@ -247,11 +499,39 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" source_span: dependency: transitive description: @@ -280,6 +560,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -301,6 +588,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: @@ -308,6 +609,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" win32: dependency: transitive description: @@ -322,6 +637,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0+2" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" sdks: dart: ">=2.18.0 <3.0.0" flutter: ">=3.3.0" diff --git a/complete_example/pubspec.yaml b/complete_example/pubspec.yaml index e32a234..be6ab56 100644 --- a/complete_example/pubspec.yaml +++ b/complete_example/pubspec.yaml @@ -11,14 +11,22 @@ environment: dependencies: flutter: sdk: flutter - go_router: ^5.0.5 + go_router: ^5.1.9 flutter_riverpod: ^2.1.1 + riverpod_annotation: ^1.0.6 shared_preferences: ^2.0.15 + freezed_annotation: ^2.2.0 + json_annotation: ^4.7.0 dev_dependencies: + build_runner: ^2.3.2 flutter_test: sdk: flutter flutter_lints: ^2.0.1 + go_router_builder: ^1.0.15 + riverpod_generator: ^1.0.6 + freezed: ^2.2.1 + json_serializable: ^6.5.4 flutter: uses-material-design: true