diff --git a/lib/base/base_api.dart b/lib/base/base_api.dart index 2f1fe00..19ae163 100644 --- a/lib/base/base_api.dart +++ b/lib/base/base_api.dart @@ -2,10 +2,9 @@ import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; class BaseApi { - BaseApi({@required this.client}); + BaseApi({required this.client}); final http.Client client; diff --git a/lib/base/dependencies.dart b/lib/base/dependencies.dart new file mode 100644 index 0000000..814d58b --- /dev/null +++ b/lib/base/dependencies.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_workshop/feature/home/home_bloc.dart'; +import 'package:flutter_workshop/feature/login/login_bloc.dart'; +import 'package:flutter_workshop/model/donation/donation.dart'; +import 'package:flutter_workshop/model/donation/donation_api.dart'; +import 'package:flutter_workshop/model/login/login_api.dart'; +import 'package:flutter_workshop/model/login/login_response.dart'; +import 'package:flutter_workshop/service/session.dart'; +import 'package:flutter_workshop/service/shared_preferences_storage.dart'; +import 'package:flutter_workshop/util/http_event.dart'; +import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; +import 'package:provider/single_child_widget.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future> getDependencies() async { + final http.Client _httpClient = http.Client(); + + final SharedPreferences _sharedPreferences = + await SharedPreferences.getInstance(); + + final SharedPreferencesStorage _sharedPreferencesStorage = + SharedPreferencesStorage(_sharedPreferences); + + final Session _session = + Session(diskStorageProvider: _sharedPreferencesStorage); + + await SystemChrome.setPreferredOrientations( + [ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ], + ); + + return [ + Provider( + create: (_) => LoginBloc( + controller: StreamController>.broadcast(), + loginApi: LoginApi(client: _httpClient), + sessionProvider: _session, + ), + ), + Provider( + create: (_) => HomeBloc( + controller: StreamController>.broadcast(), + donationApi: DonationApi(client: _httpClient), + diskStorageProvider: _sharedPreferencesStorage, + sessionProvider: _session, + ), + ), + ]; +} diff --git a/lib/base/my_app.dart b/lib/base/my_app.dart index e838988..33437eb 100644 --- a/lib/base/my_app.dart +++ b/lib/base/my_app.dart @@ -5,18 +5,18 @@ import 'package:provider/single_child_widget.dart'; class MyApp extends StatefulWidget { const MyApp({ - Key key, + Key? key, this.dependencies, }) : super(key: key); - final List dependencies; + final List? dependencies; @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { - List _appDependencies; + List? _appDependencies; @override void initState() { @@ -27,7 +27,7 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MultiProvider( - providers: _appDependencies, + providers: _appDependencies!, child: BaseMaterialApp(), ); } diff --git a/lib/config/l10n.dart b/lib/config/l10n.dart index d4fee57..704697c 100644 --- a/lib/config/l10n.dart +++ b/lib/config/l10n.dart @@ -15,7 +15,7 @@ class StringLocalizationsDelegate extends LocalizationsDelegate { this.testingLocale, }); - final Locale testingLocale; + final Locale? testingLocale; @override bool isSupported(Locale locale) => @@ -38,28 +38,29 @@ class L10n { }); final Locale _locale; - final Locale testingLocale; + final Locale? testingLocale; final String defaultLanguageCode = supportedLanguageCodes.first; final Map> _localizedValues = Strings().map; String _get(String key) { if (_localizedValues[key] == null) return key; - final languageCode = testingLocale ?? _locale; + final languageCode = testingLocale?.languageCode ?? _locale.languageCode; - return _localizedValues[key][languageCode] ?? - _localizedValues[key][defaultLanguageCode] ?? + return _localizedValues[key]![languageCode] ?? + _localizedValues[key]![defaultLanguageCode] ?? key; } - static String getString( + static String? getString( BuildContext context, - String key, + String? key, ) => key == null ? null : Localizations.of( context, L10n, - )._get(key); + )! + ._get(key); } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 137fe8b..86d16eb 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -7,14 +7,14 @@ import 'package:flutter_workshop/feature/login/login_bloc.dart'; import 'package:flutter_workshop/util/navigation.dart'; import 'package:provider/provider.dart'; -MaterialPageRoute getRouteFactory(settings) { - MaterialPageRoute route; +MaterialPageRoute? getRouteFactory(settings) { + MaterialPageRoute? route; switch (settings.name) { case Home.routeName: { route = Navigation.makeRoute(Consumer( builder: (context, bloc, child) => Home(bloc: bloc), - )); + )) as MaterialPageRoute?; } break; @@ -22,7 +22,7 @@ MaterialPageRoute getRouteFactory(settings) { { route = Navigation.makeRoute(Consumer( builder: (context, bloc, child) => Login(bloc: bloc), - )); + )) as MaterialPageRoute?; } break; @@ -31,7 +31,7 @@ MaterialPageRoute getRouteFactory(settings) { final DetailArguments args = settings.arguments; route = Navigation.makeRoute(Detail( donation: args.donation, - )); + )) as MaterialPageRoute?; } break; } diff --git a/lib/custom/adaptive_view.dart b/lib/custom/adaptive_view.dart index 0beabc9..96c3357 100644 --- a/lib/custom/adaptive_view.dart +++ b/lib/custom/adaptive_view.dart @@ -4,13 +4,13 @@ const double MEDUIM_SCREEN_MIN_WIDTH = 720; const double LARGE_SCREEN_MIN_WIDTH = 1200; class AdaptiveView extends StatelessWidget { - final Widget smallView; - final Widget mediumView; - final Widget largeView; - final FormFactor forcedFormFactor; + final Widget? smallView; + final Widget? mediumView; + final Widget? largeView; + final FormFactor? forcedFormFactor; const AdaptiveView({ - Key key, + Key? key, this.smallView, this.mediumView, this.largeView, @@ -23,7 +23,7 @@ class AdaptiveView extends StatelessWidget { final formFactor = forcedFormFactor ?? _getFormFactor(MediaQuery.of(context).size.width); - return _getView(formFactor); + return _getView(formFactor)!; } FormFactor _getFormFactor(double width) { @@ -38,14 +38,12 @@ class AdaptiveView extends StatelessWidget { return FormFactor.small; } - Widget _getView(FormFactor formFactor) { + Widget? _getView(FormFactor formFactor) { switch (formFactor) { case FormFactor.large: return largeView ?? mediumView ?? smallView; - break; case FormFactor.medium: return mediumView ?? largeView ?? smallView; - break; default: return smallView ?? mediumView ?? largeView; } diff --git a/lib/custom/custom_alert_dialog.dart b/lib/custom/custom_alert_dialog.dart index c7cc482..10367bd 100644 --- a/lib/custom/custom_alert_dialog.dart +++ b/lib/custom/custom_alert_dialog.dart @@ -6,7 +6,7 @@ import 'package:flutter_workshop/config/platform_independent_constants.dart'; class CustomAlertDialog extends StatelessWidget { const CustomAlertDialog({ - Key key, + Key? key, this.onConfirmed, this.confirmationText, this.cancellationText, @@ -15,17 +15,17 @@ class CustomAlertDialog extends StatelessWidget { this.hasCancelButton = false, }) : super(key: key); - final VoidCallback onConfirmed; - final String confirmationText; - final String cancellationText; - final String titleText; - final String contentText; + final VoidCallback? onConfirmed; + final String? confirmationText; + final String? cancellationText; + final String? titleText; + final String? contentText; final bool hasCancelButton; @override Widget build(BuildContext context) { - final Text title = titleText == null ? null : Text(titleText); - final Text content = contentText == null ? null : Text(contentText); + final Text? title = titleText == null ? null : Text(titleText!); + final Text? content = contentText == null ? null : Text(contentText!); return AlertDialog( title: title, diff --git a/lib/custom/custom_app_bar.dart b/lib/custom/custom_app_bar.dart index e4b86bc..3c560e3 100644 --- a/lib/custom/custom_app_bar.dart +++ b/lib/custom/custom_app_bar.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { - const CustomAppBar({Key key, this.actions, this.title}) : super(key: key); + const CustomAppBar({Key? key, this.actions, this.title}) : super(key: key); - final List actions; - final String title; + final List? actions; + final String? title; @override Widget build(BuildContext context) { - final String computedTitle = title == null ? '' : title; + final String computedTitle = title == null ? '' : title!; return AppBar( brightness: Brightness.light, diff --git a/lib/custom/custom_button.dart b/lib/custom/custom_button.dart index d23207e..1f574a8 100644 --- a/lib/custom/custom_button.dart +++ b/lib/custom/custom_button.dart @@ -5,9 +5,9 @@ class PrimaryContainedButton extends StatelessWidget { final VoidCallback onPressed; const PrimaryContainedButton({ - Key key, - @required this.child, - @required this.onPressed, + Key? key, + required this.child, + required this.onPressed, }) : super(key: key); @override @@ -31,13 +31,13 @@ class PrimaryContainedButton extends StatelessWidget { } class PrimaryTextButton extends StatelessWidget { - final String text; - final VoidCallback onPressed; + final String? text; + final VoidCallback? onPressed; const PrimaryTextButton({ - Key key, - @required this.text, - @required this.onPressed, + Key? key, + required this.text, + required this.onPressed, }) : super(key: key); @override @@ -55,7 +55,7 @@ class PrimaryTextButton extends StatelessWidget { style: flatButtonStyle, onPressed: onPressed, child: Text( - text, + text!, style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/lib/feature/detail/detail.dart b/lib/feature/detail/detail.dart index aa9ad23..f468492 100644 --- a/lib/feature/detail/detail.dart +++ b/lib/feature/detail/detail.dart @@ -5,12 +5,12 @@ import 'package:flutter_workshop/model/user/user.dart'; class Detail extends StatelessWidget { static const routeName = '/detail'; - final Donation donation; + final Donation? donation; final bool showAppBar; const Detail({ - Key key, - @required this.donation, + Key? key, + required this.donation, this.showAppBar = true, }) : super(key: key); @@ -24,9 +24,9 @@ class Detail extends StatelessWidget { } class _Body extends StatelessWidget { - final Donation donation; + final Donation? donation; - const _Body({Key key, @required this.donation}) : super(key: key); + const _Body({Key? key, required this.donation}) : super(key: key); @override Widget build(BuildContext context) { @@ -36,11 +36,11 @@ class _Body extends StatelessWidget { children: [ _Carousel(donation: donation), _VerticalMargin(), - _Title(title: donation.title), + _Title(title: donation!.title), _VerticalMargin(), - Text(donation.description), + Text(donation!.description!), _VerticalMargin(), - _Donator(user: donation.user), + _Donator(user: donation!.user), ], ), ); @@ -48,11 +48,11 @@ class _Body extends StatelessWidget { } class _Carousel extends StatelessWidget { - final Donation donation; + final Donation? donation; const _Carousel({ - Key key, - @required this.donation, + Key? key, + required this.donation, }) : super(key: key); @override @@ -62,9 +62,9 @@ class _Carousel extends StatelessWidget { height: size, child: ListView.builder( scrollDirection: Axis.horizontal, - itemCount: donation.images.length, + itemCount: donation!.images.length, itemBuilder: (BuildContext context, int index) { - final String url = donation.images[index].url; + final String? url = donation!.images[index].url; return _Image( url: url, size: size, @@ -76,13 +76,13 @@ class _Carousel extends StatelessWidget { } class _Image extends StatelessWidget { - final String url; + final String? url; final double size; const _Image({ - Key key, - @required this.url, - @required this.size, + Key? key, + required this.url, + required this.size, }) : super(key: key); @override @@ -92,7 +92,7 @@ class _Image extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(10.0), child: Image.network( - url, + url!, height: size, width: size, fit: BoxFit.cover, @@ -103,14 +103,14 @@ class _Image extends StatelessWidget { } class _Title extends StatelessWidget { - final String title; + final String? title; - const _Title({Key key, @required this.title}) : super(key: key); + const _Title({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { return Text( - title, + title!, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w600, @@ -122,19 +122,19 @@ class _Title extends StatelessWidget { class _Donator extends StatelessWidget { final User user; - const _Donator({Key key, @required this.user}) : super(key: key); + const _Donator({Key? key, required this.user}) : super(key: key); @override Widget build(BuildContext context) { - final ImageProvider backgroundImage = user.avatarUrl == null + final ImageProvider? backgroundImage = user.avatarUrl == null ? null : NetworkImage( - user.avatarUrl, + user.avatarUrl!, ); - final Widget child = user.avatarUrl == null + final Widget? child = user.avatarUrl == null ? Text( - user.name[0].toUpperCase(), + user.name![0].toUpperCase(), ) : null; @@ -147,7 +147,7 @@ class _Donator extends StatelessWidget { foregroundColor: Colors.white, ), const SizedBox(width: 16), - Text(user.name) + Text(user.name!) ], ); } @@ -161,7 +161,7 @@ class _VerticalMargin extends StatelessWidget { } class DetailArguments { - final Donation donation; + final Donation? donation; DetailArguments({this.donation}); } diff --git a/lib/feature/home/home.dart b/lib/feature/home/home.dart index 2afd88c..cbee0b6 100644 --- a/lib/feature/home/home.dart +++ b/lib/feature/home/home.dart @@ -18,27 +18,27 @@ class Home extends StatefulWidget { static const Key largeScreenCTAKey = Key(homeLargeScreenCTAKey); static const routeName = '/home'; - final HomeBloc bloc; + final HomeBloc? bloc; - const Home({Key key, this.bloc}) : super(key: key); + const Home({Key? key, this.bloc}) : super(key: key); @override _HomeState createState() => _HomeState(); } class _HomeState extends State { - Navigation _navigation; + late Navigation _navigation; @override void initState() { super.initState(); _navigation = Navigation(context); - widget.bloc.loadDonations(); + widget.bloc!.loadDonations(); } @override Widget build(BuildContext context) { - final donationListStream = widget.bloc.stream; + final donationListStream = widget.bloc!.stream; return Scaffold( appBar: CustomAppBar( @@ -56,9 +56,9 @@ class _HomeState extends State { List _buildAppBarActions() { return [ - FutureBuilder( - future: widget.bloc.loadCurrentUser(), - builder: (BuildContext context, AsyncSnapshot snapshot) { + FutureBuilder( + future: widget.bloc!.loadCurrentUser(), + builder: (BuildContext context, AsyncSnapshot snapshot) { return snapshot.hasData ? _UserAvatar( user: snapshot.data, @@ -73,7 +73,7 @@ class _HomeState extends State { } Future _logout() async { - await widget.bloc.logout(); + await widget.bloc!.logout(); setState(() {}); return Navigator.of(context).pop(); } @@ -103,9 +103,9 @@ class _DonationListStreamBuilder extends StatelessWidget { final Function(Donation) onTapListItem; const _DonationListStreamBuilder({ - Key key, - @required this.stream, - @required this.onTapListItem, + Key? key, + required this.stream, + required this.onTapListItem, }) : super(key: key); @override @@ -145,11 +145,11 @@ class _DonationListStreamBuilder extends StatelessWidget { } class _LargeScreenView extends StatefulWidget { - final List list; + final List? list; const _LargeScreenView({ - Key key, - @required this.list, + Key? key, + required this.list, }) : super(key: key); @override @@ -157,7 +157,7 @@ class _LargeScreenView extends StatefulWidget { } class __LargeScreenViewState extends State<_LargeScreenView> { - Donation _selectedDonation; + Donation? _selectedDonation; @override Widget build(BuildContext context) { @@ -182,7 +182,7 @@ class __LargeScreenViewState extends State<_LargeScreenView> { decoration: BoxDecoration( border: Border( left: BorderSide( - color: Colors.grey[300], + color: Colors.grey[300]!, width: 1.0, ), ), @@ -202,7 +202,7 @@ class __LargeScreenViewState extends State<_LargeScreenView> { } class _LargeScreenCTA extends StatelessWidget { - const _LargeScreenCTA({Key key}) : super(key: key); + const _LargeScreenCTA({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -212,21 +212,21 @@ class _LargeScreenCTA extends StatelessWidget { L10n.getString( context, 'home_large_screen_call_to_action', - ), + )!, ), ); } } class _ListView extends StatelessWidget { - final List list; + final List? list; final Function(Donation) onTapItem; - final int selectedDonationId; + final int? selectedDonationId; const _ListView({ - Key key, - @required this.list, - @required this.onTapItem, + Key? key, + required this.list, + required this.onTapItem, this.selectedDonationId, }) : super(key: key); @@ -236,9 +236,9 @@ class _ListView extends StatelessWidget { isAlwaysShown: true, child: ListView.builder( padding: const EdgeInsets.only(top: 8), - itemCount: list.length, + itemCount: list!.length, itemBuilder: (BuildContext context, int index) { - final Donation listItem = list[index]; + final Donation listItem = list![index]; final color = listItem.id == selectedDonationId ? Theme.of(context).primaryColor.withOpacity(0.25) @@ -261,23 +261,23 @@ class _ListView extends StatelessWidget { } class _UserAvatar extends StatelessWidget { - final User user; + final User? user; final GestureTapCallback onTap; const _UserAvatar({ - Key key, - @required this.user, - @required this.onTap, + Key? key, + required this.user, + required this.onTap, }) : super(key: key); @override Widget build(BuildContext context) { - Widget child = Text(user.name.substring(0, 1).toUpperCase()); - ImageProvider backgroundImage; + Widget? child = Text(user!.name!.substring(0, 1).toUpperCase()); + ImageProvider? backgroundImage; - if (user.avatarUrl != null && user.name.isNotEmpty) { + if (user!.avatarUrl != null && user!.name!.isNotEmpty) { child = null; - backgroundImage = NetworkImage(user.avatarUrl); + backgroundImage = NetworkImage(user!.avatarUrl!); } return GestureDetector( @@ -294,15 +294,15 @@ class _UserAvatar extends StatelessWidget { } class _ListItemTile extends StatelessWidget { - final int index; + final int? index; final Donation donation; final GestureTapCallback onTap; const _ListItemTile({ - Key key, + Key? key, this.index, - @required this.donation, - @required this.onTap, + required this.donation, + required this.onTap, }) : super(key: key); @override @@ -321,14 +321,14 @@ class _ListItemTile extends StatelessWidget { class _DonationImage extends StatelessWidget { final List images; - const _DonationImage({Key key, @required this.images}) : super(key: key); + const _DonationImage({Key? key, required this.images}) : super(key: key); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(5.0), child: Image.network( - images.first.url, + images.first.url!, height: 75, width: 75, fit: BoxFit.cover, @@ -338,14 +338,14 @@ class _DonationImage extends StatelessWidget { } class _DonationTitle extends StatelessWidget { - final String text; + final String? text; - const _DonationTitle({Key key, @required this.text}) : super(key: key); + const _DonationTitle({Key? key, required this.text}) : super(key: key); @override Widget build(BuildContext context) { return Text( - text, + text!, maxLines: 1, overflow: TextOverflow.ellipsis, ); @@ -353,16 +353,16 @@ class _DonationTitle extends StatelessWidget { } class _DonationSubtitle extends StatelessWidget { - final String text; + final String? text; - const _DonationSubtitle({Key key, @required this.text}) : super(key: key); + const _DonationSubtitle({Key? key, required this.text}) : super(key: key); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 4.0), child: Text( - text, + text!, maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -371,9 +371,9 @@ class _DonationSubtitle extends StatelessWidget { } class _LoginButton extends StatelessWidget { - final VoidCallback onPressed; + final VoidCallback? onPressed; - const _LoginButton({Key key, this.onPressed}) : super(key: key); + const _LoginButton({Key? key, this.onPressed}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/feature/home/home_bloc.dart b/lib/feature/home/home_bloc.dart index a0e3a8c..6edbab6 100644 --- a/lib/feature/home/home_bloc.dart +++ b/lib/feature/home/home_bloc.dart @@ -9,10 +9,10 @@ import 'package:meta/meta.dart'; class HomeBloc { HomeBloc({ - @required this.donationApi, - @required this.controller, - @required this.diskStorageProvider, - @required this.sessionProvider, + required this.donationApi, + required this.controller, + required this.diskStorageProvider, + required this.sessionProvider, }); final DonationApi donationApi; @@ -33,7 +33,7 @@ class HomeBloc { } } - Future loadCurrentUser() async => diskStorageProvider.getUser(); + Future loadCurrentUser() async => diskStorageProvider.getUser(); Future> logout() async => sessionProvider.logUserOut(); } diff --git a/lib/feature/login/login.dart b/lib/feature/login/login.dart index 23090ea..b616353 100644 --- a/lib/feature/login/login.dart +++ b/lib/feature/login/login.dart @@ -22,9 +22,9 @@ class Login extends StatefulWidget { static const Key passwordVisibilityToggledKey = Key(loginPasswordVisibilityToggleValueKey); - final LoginBloc bloc; + final LoginBloc? bloc; - const Login({Key key, this.bloc}) : super(key: key); + const Login({Key? key, this.bloc}) : super(key: key); @override _LoginState createState() => _LoginState(); @@ -57,7 +57,7 @@ class _LoginState extends State { } void _listenForLoginResponse() { - widget.bloc.stream.listen((HttpEvent event) { + widget.bloc!.stream.listen((HttpEvent event) { if (event.isLoading) { return; } @@ -78,12 +78,12 @@ class _LoginState extends State { ); } - Future _onLoginFailure(int statusCode) { + Future _onLoginFailure(int? statusCode) { final Map map = { HttpStatus.badRequest: 'login_error_bad_credentials' }; - final String messageKey = map[statusCode] ?? 'common_error_server_generic'; + final String messageKey = map[statusCode!] ?? 'common_error_server_generic'; return showDialog( context: context, @@ -96,13 +96,13 @@ class _LoginState extends State { class SmallScreenView extends StatelessWidget { const SmallScreenView({ - Key key, - @required this.bloc, + Key? key, + required this.bloc, this.horizontalPadding, }) : super(key: key); - final LoginBloc bloc; - final double horizontalPadding; + final LoginBloc? bloc; + final double? horizontalPadding; @override Widget build(BuildContext context) { @@ -120,22 +120,22 @@ class SmallScreenView extends StatelessWidget { class LargeScreenView extends StatelessWidget { const LargeScreenView({ - Key key, - @required this.bloc, + Key? key, + required this.bloc, this.horizontalPadding, this.formFlexFactor, this.headlineStyle, }) : super(key: key); - final LoginBloc bloc; - final double horizontalPadding; - final int formFlexFactor; - final TextStyle headlineStyle; + final LoginBloc? bloc; + final double? horizontalPadding; + final int? formFlexFactor; + final TextStyle? headlineStyle; @override Widget build(BuildContext context) { final theme = Theme.of(context); - final texStyle = headlineStyle ?? theme.textTheme.headline4; + final texStyle = headlineStyle ?? theme.textTheme.headline4!; return Row( children: [ @@ -163,7 +163,7 @@ class LargeScreenView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - L10n.getString(context, 'login_welcome'), + L10n.getString(context, 'login_welcome')!, style: texStyle.copyWith( color: Colors.white, ), @@ -179,9 +179,9 @@ class LargeScreenView extends StatelessWidget { } class LoginForm extends StatefulWidget { - final LoginBloc bloc; + final LoginBloc? bloc; - const LoginForm({Key key, @required this.bloc}) : super(key: key); + const LoginForm({Key? key, required this.bloc}) : super(key: key); @override _LoginFormState createState() => _LoginFormState(); @@ -212,13 +212,13 @@ class _LoginFormState extends State { ), const _VerticalSpacer(), StreamBuilder>( - stream: widget.bloc.stream, + stream: widget.bloc!.stream, builder: ( BuildContext context, AsyncSnapshot> snapshot, ) { final bool isLoading = - snapshot.hasData && snapshot.data.isLoading; + snapshot.hasData && snapshot.data!.isLoading; return _SubmitButton( formState: _formKey.currentState, isLoading: isLoading, @@ -233,7 +233,7 @@ class _LoginFormState extends State { ); } - Future _sendLoginRequest() => widget.bloc.login( + Future _sendLoginRequest() => widget.bloc!.login( email: _emailController.text, password: _passwordController.text, ); @@ -243,8 +243,8 @@ class _EmailField extends StatelessWidget { final TextEditingController controller; const _EmailField({ - Key key, - @required this.controller, + Key? key, + required this.controller, }) : super(key: key); @override @@ -258,9 +258,9 @@ class _EmailField extends StatelessWidget { 'login_email', ), ), - validator: (String input) => L10n.getString( + validator: (String? input) => L10n.getString( context, - validateEmail(input), + validateEmail(input!), ), ); } @@ -272,10 +272,10 @@ class _PasswordField extends StatelessWidget { final VoidCallback onVisibilityTogglePressed; const _PasswordField({ - Key key, - @required this.controller, - @required this.isPasswordVisible, - @required this.onVisibilityTogglePressed, + Key? key, + required this.controller, + required this.isPasswordVisible, + required this.onVisibilityTogglePressed, }) : super(key: key); @override @@ -294,9 +294,9 @@ class _PasswordField extends StatelessWidget { ), ), obscureText: !isPasswordVisible, - validator: (String input) => L10n.getString( + validator: (String? input) => L10n.getString( context, - validatePassword(input), + validatePassword(input!), ), ); } @@ -307,9 +307,9 @@ class _PasswordVisibilityToggle extends StatelessWidget { final VoidCallback onPressed; const _PasswordVisibilityToggle({ - Key key, - @required this.isPasswordVisible, - @required this.onPressed, + Key? key, + required this.isPasswordVisible, + required this.onPressed, }) : super(key: key); @override @@ -347,27 +347,27 @@ class _PasswordVisibilityToggle extends StatelessWidget { class _SubmitButton extends StatelessWidget { final bool isLoading; - final FormState formState; + final FormState? formState; final VoidCallback onPressed; const _SubmitButton({ - Key key, - @required this.isLoading, - @required this.formState, - @required this.onPressed, + Key? key, + required this.isLoading, + required this.formState, + required this.onPressed, }) : super(key: key); @override Widget build(BuildContext context) { final Widget child = isLoading ? const _ButtonProgressIndicator() - : Text(L10n.getString(context, 'login_title')); + : Text(L10n.getString(context, 'login_title')!); return PrimaryContainedButton( key: Login.submitButtonKey, child: child, onPressed: () { - if (formState.validate()) { + if (formState!.validate()) { onPressed.call(); } }, @@ -402,7 +402,7 @@ class _CredentialsHint extends StatelessWidget { L10n.getString( context, 'login_try_these_creds', - ), + )!, style: const TextStyle( color: Colors.grey, ), diff --git a/lib/feature/login/login_bloc.dart b/lib/feature/login/login_bloc.dart index 455b909..d0d5cee 100644 --- a/lib/feature/login/login_bloc.dart +++ b/lib/feature/login/login_bloc.dart @@ -6,13 +6,12 @@ import 'package:flutter_workshop/model/login/login_response.dart'; import 'package:flutter_workshop/model/user/user.dart'; import 'package:flutter_workshop/service/session_provider.dart'; import 'package:flutter_workshop/util/http_event.dart'; -import 'package:meta/meta.dart'; class LoginBloc { LoginBloc({ - @required this.loginApi, - @required this.controller, - @required this.sessionProvider, + required this.loginApi, + required this.controller, + required this.sessionProvider, }); final LoginApi loginApi; @@ -23,7 +22,7 @@ class LoginBloc { Future dispose() => controller.close(); - Future login({String email, String password}) async { + Future login({String? email, String? password}) async { try { final LoginRequest request = LoginRequest( email: email, @@ -34,7 +33,7 @@ class LoginBloc { final HttpEvent event = await loginApi.login(request); - if (event.data != null) await _saveToPreferences(event.data); + if (event.data != null) await _saveToPreferences(event.data!); controller.sink.add(event); } catch (error) { diff --git a/lib/main.dart b/lib/main.dart index e87a2be..3bfbe58 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,62 +1,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter_workshop/base/dependencies.dart'; import 'package:flutter_workshop/base/my_app.dart'; -import 'package:flutter_workshop/feature/home/home_bloc.dart'; -import 'package:flutter_workshop/feature/login/login_bloc.dart'; -import 'package:flutter_workshop/model/donation/donation.dart'; -import 'package:flutter_workshop/model/donation/donation_api.dart'; -import 'package:flutter_workshop/model/login/login_api.dart'; -import 'package:flutter_workshop/model/login/login_response.dart'; -import 'package:flutter_workshop/service/session.dart'; -import 'package:flutter_workshop/service/shared_preferences_storage.dart'; -import 'package:flutter_workshop/util/http_event.dart'; -import 'package:http/http.dart' as http; -import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; -import 'package:shared_preferences/shared_preferences.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - final http.Client _httpClient = http.Client(); - - final SharedPreferences _sharedPreferences = - await SharedPreferences.getInstance(); - - final SharedPreferencesStorage _sharedPreferencesStorage = - SharedPreferencesStorage(_sharedPreferences); - - final Session _session = - Session(diskStorageProvider: _sharedPreferencesStorage); - - await SystemChrome.setPreferredOrientations( - [ - DeviceOrientation.portraitUp, - DeviceOrientation.portraitDown, - DeviceOrientation.landscapeLeft, - DeviceOrientation.landscapeRight, - ], - ); - - List providers = [ - Provider( - create: (_) => LoginBloc( - controller: StreamController>.broadcast(), - loginApi: LoginApi(client: _httpClient), - sessionProvider: _session, - ), - ), - Provider( - create: (_) => HomeBloc( - controller: StreamController>.broadcast(), - donationApi: DonationApi(client: _httpClient), - diskStorageProvider: _sharedPreferencesStorage, - sessionProvider: _session, - ), - ), - ]; + List providers = await getDependencies(); runApp(MyApp(dependencies: providers)); } diff --git a/lib/model/donation/donation.dart b/lib/model/donation/donation.dart index 071ca1e..ca0ef21 100644 --- a/lib/model/donation/donation.dart +++ b/lib/model/donation/donation.dart @@ -6,9 +6,9 @@ part 'donation.g.dart'; @JsonSerializable() class Donation { - final int id; - final String title; - final String description; + final int? id; + final String? title; + final String? description; final User user; @JsonKey(name: 'listing_images') final List images; @@ -24,7 +24,7 @@ class Donation { factory Donation.fromJson(Map json) => _$DonationFromJson(json); - factory Donation.fake({User user}) { + factory Donation.fake({User? user}) { const id = 1; const title = 'Donation'; const description = diff --git a/lib/model/donation/donation_api.dart b/lib/model/donation/donation_api.dart index 0c85098..8a2a581 100644 --- a/lib/model/donation/donation_api.dart +++ b/lib/model/donation/donation_api.dart @@ -7,7 +7,7 @@ import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; class DonationApi extends BaseApi { - DonationApi({@required http.Client client}) : super(client: client); + DonationApi({required http.Client client}) : super(client: client); Future> getDonations() async { final String url = '${baseUrl}users/4/listings'; diff --git a/lib/model/donation/donation_image.dart b/lib/model/donation/donation_image.dart index 09772fb..abe12e8 100644 --- a/lib/model/donation/donation_image.dart +++ b/lib/model/donation/donation_image.dart @@ -4,7 +4,7 @@ part 'donation_image.g.dart'; @JsonSerializable() class DonationImage { - final String url; + final String? url; DonationImage(this.url); diff --git a/lib/model/login/login_api.dart b/lib/model/login/login_api.dart index e22259c..61f95e9 100644 --- a/lib/model/login/login_api.dart +++ b/lib/model/login/login_api.dart @@ -9,13 +9,13 @@ import 'package:flutter_workshop/model/login/login_response.dart'; import 'package:meta/meta.dart'; class LoginApi extends BaseApi { - LoginApi({@required http.Client client}) : super(client: client); + LoginApi({required http.Client client}) : super(client: client); Future> login(LoginRequest request) async { const String url = 'https://reqres.in/api/login'; final http.Response response = await post(url, request.toJson()); - LoginResponse data; + LoginResponse? data; if (response.statusCode == HttpStatus.ok) { data = LoginResponse.fromJson(jsonDecode(response.body)); diff --git a/lib/model/login/login_request.dart b/lib/model/login/login_request.dart index ab83d54..82fae3a 100644 --- a/lib/model/login/login_request.dart +++ b/lib/model/login/login_request.dart @@ -4,8 +4,8 @@ class LoginRequest { this.password, }); - final String email; - final String password; + final String? email; + final String? password; Map toJson() => { 'email': email, diff --git a/lib/model/login/login_response.dart b/lib/model/login/login_response.dart index 13cd1c3..0cdea49 100644 --- a/lib/model/login/login_response.dart +++ b/lib/model/login/login_response.dart @@ -4,7 +4,7 @@ part 'login_response.g.dart'; @JsonSerializable() class LoginResponse { - final String token; + final String? token; LoginResponse(this.token); diff --git a/lib/model/user/user.dart b/lib/model/user/user.dart index 6342c53..eb2897b 100644 --- a/lib/model/user/user.dart +++ b/lib/model/user/user.dart @@ -6,10 +6,10 @@ part 'user.g.dart'; @JsonSerializable() class User { - int id; - String name; + int? id; + String? name; @JsonKey(name: 'image_url') - String avatarUrl; + String? avatarUrl; User( this.id, @@ -20,10 +20,10 @@ class User { factory User.fromJson(Map json) => _$UserFromJson(json); factory User.fromEncodedJson(String encoded) => - encoded == null ? null : User.fromJson(jsonDecode(encoded)); + User.fromJson(jsonDecode(encoded)); User.fake({ - String avatarUrl = + String? avatarUrl = 'https://firebasestorage.googleapis.com/v0/b/givapp-938de.appspot.com/o/randomuser_women_30.jpeg?alt=media&token=823cbd79-f1f1-4c83-8f6d-8f8fea537f2c', }) : id = 1, name = 'Eve Holt', diff --git a/lib/service/disk_storage_provider.dart b/lib/service/disk_storage_provider.dart index b821864..bd6ffbc 100644 --- a/lib/service/disk_storage_provider.dart +++ b/lib/service/disk_storage_provider.dart @@ -2,9 +2,9 @@ import 'package:flutter_workshop/model/user/user.dart'; abstract class DiskStorageProvider { Future setUser(User user); - User getUser(); + User? getUser(); Future clearUser(); - Future setAccessToken(String token); - String getAccessToken(); + Future setAccessToken(String? token); + String? getAccessToken(); Future clearToken(); } diff --git a/lib/service/session.dart b/lib/service/session.dart index 12cbe17..6af53d2 100644 --- a/lib/service/session.dart +++ b/lib/service/session.dart @@ -1,17 +1,16 @@ import 'package:flutter_workshop/model/user/user.dart'; import 'package:flutter_workshop/service/disk_storage_provider.dart'; -import 'package:meta/meta.dart'; import 'session_provider.dart'; class Session implements SessionProvider { - Session({@required this.diskStorageProvider}); + Session({required this.diskStorageProvider}); final DiskStorageProvider diskStorageProvider; @override Future> logUserIn( - String token, + String? token, User user, ) => Future.wait( diff --git a/lib/service/session_provider.dart b/lib/service/session_provider.dart index 73276e7..496d076 100644 --- a/lib/service/session_provider.dart +++ b/lib/service/session_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_workshop/model/user/user.dart'; abstract class SessionProvider { - Future> logUserIn(String token, User user); + Future> logUserIn(String? token, User user); Future> logUserOut(); } diff --git a/lib/service/shared_preferences_storage.dart b/lib/service/shared_preferences_storage.dart index 7e2ffc5..982e676 100644 --- a/lib/service/shared_preferences_storage.dart +++ b/lib/service/shared_preferences_storage.dart @@ -11,13 +11,13 @@ class SharedPreferencesStorage implements DiskStorageProvider { final SharedPreferences sharedPreferences; @override - User getUser() { + User? getUser() { try { - return User.fromEncodedJson( - sharedPreferences.getString( - _userKey, - ), + final encodedJson = sharedPreferences.getString( + _userKey, ); + + return encodedJson == null ? null : User.fromEncodedJson(encodedJson); } catch (e) { return null; } @@ -30,14 +30,14 @@ class SharedPreferencesStorage implements DiskStorageProvider { ); @override - String getAccessToken() => sharedPreferences.getString( + String? getAccessToken() => sharedPreferences.getString( _accessTokenKey, ); @override - Future setAccessToken(String token) => sharedPreferences.setString( + Future setAccessToken(String? token) => sharedPreferences.setString( _accessTokenKey, - token, + token!, ); @override diff --git a/lib/util/custom_form_field_validator.dart b/lib/util/custom_form_field_validator.dart index 07e3d74..f9b33ce 100644 --- a/lib/util/custom_form_field_validator.dart +++ b/lib/util/custom_form_field_validator.dart @@ -1,7 +1,7 @@ -String validateEmail(String input) { +String? validateEmail(String input) { const Pattern pattern = r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; - final RegExp regex = RegExp(pattern); + final RegExp regex = RegExp(pattern as String); if (input.trim().isEmpty) { return 'validation_message_email_required'; @@ -14,7 +14,7 @@ String validateEmail(String input) { return null; } -String validatePassword(String input) { +String? validatePassword(String input) { if (input.trim().isEmpty) { return 'validation_message_password_required'; } diff --git a/lib/util/http_event.dart b/lib/util/http_event.dart index e3b62ad..739e2d2 100644 --- a/lib/util/http_event.dart +++ b/lib/util/http_event.dart @@ -6,8 +6,8 @@ class HttpEvent { }); final EventState state; - final T data; - final int statusCode; + final T? data; + final int? statusCode; bool get isLoading => state == EventState.loading; } diff --git a/lib/util/navigation.dart b/lib/util/navigation.dart index e844e94..12c58a8 100644 --- a/lib/util/navigation.dart +++ b/lib/util/navigation.dart @@ -5,9 +5,9 @@ class Navigation { final BuildContext context; - Future pushNamed( + Future pushNamed( String routeName, { - Object arguments, + Object? arguments, bool clearStack = false, }) { return clearStack diff --git a/pubspec.lock b/pubspec.lock index 2ee5650..91e934d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,35 +7,35 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "22.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "1.7.1" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" + version: "3.1.2" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.1.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.1" boolean_selector: dependency: transitive description: @@ -49,56 +49,56 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.0.2" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.5" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.0.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.3" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.1" + version: "2.0.4" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.7" + version: "7.0.0" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.3.2" + version: "5.0.0" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "7.1.0" + version: "8.0.6" characters: dependency: transitive description: @@ -119,7 +119,7 @@ packages: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" cli_util: dependency: transitive description: @@ -140,7 +140,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.0.0" collection: dependency: transitive description: @@ -154,35 +154,35 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.0" coverage: dependency: transitive description: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.15.2" + version: "1.0.3" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.3" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" + version: "2.0.1" fake_async: dependency: transitive description: @@ -203,14 +203,14 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.0.0" + version: "6.1.0" fixnum: dependency: transitive description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.11" + version: "1.0.0" flutter: dependency: "direct main" description: flutter @@ -236,6 +236,13 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -247,28 +254,28 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.1" graphs: dependency: transitive description: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.0.0" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.1" + version: "0.13.3" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.1" http_parser: dependency: transitive description: @@ -289,7 +296,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "1.0.0" js: dependency: transitive description: @@ -303,21 +310,21 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "4.0.1" json_serializable: dependency: "direct dev" description: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "3.5.1" + version: "4.1.3" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.1" matcher: dependency: transitive description: @@ -338,14 +345,14 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.6+3" + version: "1.0.0" mockito: dependency: "direct dev" description: name: mockito url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" + version: "5.0.9" nested: dependency: transitive description: @@ -359,35 +366,21 @@ packages: name: network_image_mock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" + version: "2.0.1" node_preamble: dependency: transitive description: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.8" + version: "2.0.0" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" path: dependency: transitive description: @@ -450,7 +443,7 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.2.1" provider: dependency: "direct main" description: @@ -464,28 +457,21 @@ packages: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" + version: "2.0.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "1.0.0" shared_preferences: dependency: "direct main" description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" shared_preferences_linux: dependency: transitive description: @@ -534,21 +520,21 @@ packages: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.9+2" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -560,7 +546,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+3" + version: "1.0.1" source_map_stack_trace: dependency: transitive description: @@ -581,7 +567,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -602,7 +588,7 @@ packages: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" string_scanner: dependency: transitive description: @@ -616,7 +602,7 @@ packages: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" term_glyph: dependency: transitive description: @@ -630,28 +616,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.5" + version: "1.16.8" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.3.0" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.15" + version: "0.3.19" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -672,35 +658,35 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "5.5.0" + version: "6.2.0" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+15" + version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "2.1.0" webdriver: dependency: transitive description: name: webdriver url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "3.0.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.5.3" + version: "1.0.0" win32: dependency: transitive description: @@ -721,7 +707,7 @@ packages: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" flutter: ">=1.20.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7f5da7d..63e2068 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ description: A sample Flutter application. version: 1.0.0+1 environment: - sdk: ">=2.2.2 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: @@ -22,13 +22,13 @@ dependencies: flutter_localizations: sdk: flutter - http: ^0.13.1 + http: ^0.13.3 provider: ^5.0.0 - shared_preferences: ^2.0.5 + shared_preferences: ^2.0.6 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 + cupertino_icons: ^1.0.3 dev_dependencies: flutter_driver: @@ -36,12 +36,12 @@ dev_dependencies: flutter_test: sdk: flutter - build_runner: ^1.8.1 - json_annotation: ^3.1.1 - json_serializable: ^3.5.1 - mockito: ^4.1.3 - network_image_mock: 1.1.0 - test: ^1.14.3 + build_runner: ^2.0.4 + json_annotation: ^4.0.1 + json_serializable: ^4.1.3 + mockito: ^5.0.9 + network_image_mock: ^2.0.1 + test: ^1.16.8 # For information on the generic Dart part of this file, see the diff --git a/test/adaptive_vew_test.dart b/test/adaptive_vew_test.dart index a8d8cc5..f231d73 100644 --- a/test/adaptive_vew_test.dart +++ b/test/adaptive_vew_test.dart @@ -9,10 +9,10 @@ import 'test_util/test_util.dart'; void main() { Widget makeTestableWidget({ - Widget smallView, - Widget mediumView, - Widget largeView, - FormFactor formFactor, + Widget? smallView, + Widget? mediumView, + Widget? largeView, + FormFactor? formFactor, }) { return TestUtil.makeTestableWidget( dependencies: [ diff --git a/test/custom_form_field_validator_test.dart b/test/custom_form_field_validator_test.dart index 4e0930b..b8541ee 100644 --- a/test/custom_form_field_validator_test.dart +++ b/test/custom_form_field_validator_test.dart @@ -3,22 +3,22 @@ import 'package:test/test.dart'; void main() { test('empty email returns error string', () { - final String result = validateEmail(''); + final String? result = validateEmail(''); expect(result, 'validation_message_email_required'); }); test('invalid email returns error string', () { - final String result = validateEmail('invalid email'); + final String? result = validateEmail('invalid email'); expect(result, 'validation_message_email_invalid'); }); test('empty password returns error string', () { - final String result = validatePassword(''); + final String? result = validatePassword(''); expect(result, 'validation_message_password_required'); }); test('short password returns error string', () { - final String result = validatePassword('12345'); + final String? result = validatePassword('12345'); expect(result, 'validation_message_password_too_short'); }); } diff --git a/test/detail_test.dart b/test/detail_test.dart index 3cc76e6..ba7cc00 100644 --- a/test/detail_test.dart +++ b/test/detail_test.dart @@ -66,8 +66,8 @@ void main() { ), ); - expect(find.text(fakeDonation.title), findsOneWidget); - expect(find.text(fakeDonation.description), findsOneWidget); + expect(find.text(fakeDonation.title!), findsOneWidget); + expect(find.text(fakeDonation.description!), findsOneWidget); }); }); @@ -82,7 +82,7 @@ void main() { ), ); - expect(find.text(fakeDonation.user.name), findsOneWidget); + expect(find.text(fakeDonation.user.name!), findsOneWidget); }); }); @@ -99,7 +99,7 @@ void main() { ); expect( - find.text(Donation.fake().user.name[0]), + find.text(Donation.fake().user.name![0]), findsOneWidget, ); }); diff --git a/test/donation_api_test.dart b/test/donation_api_test.dart index 4b0823e..f3f02bb 100644 --- a/test/donation_api_test.dart +++ b/test/donation_api_test.dart @@ -14,7 +14,7 @@ void main() { const String _fakeResponseBody = '[{"id":127,"title":"Livro A vida do bebĂȘ","description":"Livro do pediatra Dr Rinaldo de Lamare","geonames_city_id":"6698115","geonames_state_id":"3463504","geonames_country_id":"3469034","is_active":true,"updated_at":"2019-04-18T01:47:14.841Z","user":{"id":80,"name":"Leila Fernandes","country_calling_code":"55","phone_number":"61992457329","image_url":"https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=2184645801651822\u0026width=640\u0026ext=1558106954\u0026hash=AeSR9xd7OYoqRFTX","bio":null,"created_at":"2019-04-17T15:29:14.211Z"},"categories":[{"id":33,"simple_name":"Livros","canonical_name":"Livros"}],"listing_images":[{"id":444,"url":"https://firebasestorage.googleapis.com/v0/b/givapp-938de.appspot.com/o/listings%2F1555551883274-89d414c0-5970-11e9-f3fe-f96200bfa261.jpg?alt=media\u0026token=e8a51fc1-4388-4348-babe-693b9c1b99ae","position":0}]}]'; - when(_mockClient.get(any, headers: anyNamed('headers'))) + when(_mockClient.get(any!, headers: anyNamed('headers'))) .thenAnswer((_) async => http.Response(_fakeResponseBody, 200)); expect(await _donationApi.getDonations(), isA>()); diff --git a/test/home_bloc_test.dart b/test/home_bloc_test.dart index db76c0c..ad863c0 100644 --- a/test/home_bloc_test.dart +++ b/test/home_bloc_test.dart @@ -18,12 +18,12 @@ class MockHomeResponseStreamSink extends Mock implements StreamSink> {} void main() { - MockHomeResponseStreamController _mockController; - MockHomeResponseStreamSink _mockSink; - MockDonationApi _mockDonationApi; - MockSessionProvider _mockSessionProvider; - MockDiskStorageProvider _mockDiskStorageProvider; - HomeBloc _bloc; + late MockHomeResponseStreamController _mockController; + late MockHomeResponseStreamSink _mockSink; + late MockDonationApi _mockDonationApi; + late MockSessionProvider _mockSessionProvider; + late MockDiskStorageProvider _mockDiskStorageProvider; + late HomeBloc _bloc; setUp(() { _mockController = MockHomeResponseStreamController(); @@ -52,14 +52,14 @@ void main() { .thenAnswer((_) async => Donation.fakeList()); await _bloc.loadDonations(); - verify(_mockSink.add(any)); + verify(_mockSink.add(any!)); }); test('adds error to stream sink if api throws an exception', () async { when(_mockDonationApi.getDonations()).thenThrow(Error()); await _bloc.loadDonations(); - verify(_mockSink.addError(any)).called(1); + verify(_mockSink.addError(any!)).called(1); }); test('recovers user from disk storage', () async { diff --git a/test/home_test.dart b/test/home_test.dart index 3efe9f2..82ebdc1 100644 --- a/test/home_test.dart +++ b/test/home_test.dart @@ -22,7 +22,7 @@ import 'test_util/test_util.dart'; void main() { final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); + TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding; final MockHomeBloc _mockHomeBloc = MockHomeBloc(); final MockLoginBloc _mockLoginBloc = MockLoginBloc(); @@ -95,7 +95,7 @@ void main() { await tester.tap(loginButton); await tester.pumpAndSettle(); - verify(_mockNavigationObserver.didPush(any, any)); + verify(_mockNavigationObserver.didPush(any!, any)); expect(find.byType(Login), findsOneWidget); }); @@ -121,7 +121,7 @@ void main() { await tester.tap(firstItem); await tester.pumpAndSettle(); - verify(_mockNavigationObserver.didPush(any, any)); + verify(_mockNavigationObserver.didPush(any!, any)); expect(find.byType(Detail), findsOneWidget); await controller.close(); diff --git a/test/login_api_test.dart b/test/login_api_test.dart index e4cf800..9c09403 100644 --- a/test/login_api_test.dart +++ b/test/login_api_test.dart @@ -10,8 +10,8 @@ import 'test_util/fakes.dart'; import 'test_util/mocks.dart'; void main() { - MockClient _mockClient; - LoginApi _loginApi; + late MockClient _mockClient; + late LoginApi _loginApi; final LoginRequest _loginRequest = LoginRequest( email: 'ab@cd.com', password: '123456', @@ -25,7 +25,7 @@ void main() { test('returns LoginResponse data if the http call succeeds', () async { when( _mockClient.post( - any, + any!, body: anyNamed('body'), headers: anyNamed('headers'), ), @@ -40,7 +40,7 @@ void main() { test('returns null data if the http call fails', () async { when( _mockClient.post( - any, + any!, body: anyNamed('body'), headers: anyNamed('headers'), ), diff --git a/test/login_bloc_test.dart b/test/login_bloc_test.dart index 921e158..ae32840 100644 --- a/test/login_bloc_test.dart +++ b/test/login_bloc_test.dart @@ -18,11 +18,11 @@ class MockLoginResponseStreamSink extends Mock implements StreamSink> {} Future main() async { - MockLoginResponseStreamController _mockController; - MockLoginResponseStreamSink _mockSink; - MockLoginApi _mockLoginApi; - MockSessionProvider _mockSessionProvider; - LoginBloc _bloc; + late MockLoginResponseStreamController _mockController; + late MockLoginResponseStreamSink _mockSink; + late MockLoginApi _mockLoginApi; + late MockSessionProvider _mockSessionProvider; + late LoginBloc _bloc; setUp(() async { _mockController = MockLoginResponseStreamController(); @@ -42,34 +42,34 @@ Future main() async { test('calls login api', () async { await _bloc.login(email: 'test@test.com', password: '123456'); - verify(_mockLoginApi.login(any)); + verify(_mockLoginApi.login(any!)); }); test( 'adds loading and success events to stream sink if api returns a LoginReponse', () async { - when(_mockLoginApi.login(any)).thenAnswer( + when(_mockLoginApi.login(any!)).thenAnswer( (_) async => HttpEvent(data: LoginResponse('token'))); await _bloc.login(email: 'test@test.com', password: '123456'); - verify(_mockSink.add(any)).called(2); + verify(_mockSink.add(any!)).called(2); }); test('adds error to stream sink if api throws an exception', () async { - when(_mockLoginApi.login(any)).thenThrow(Error()); + when(_mockLoginApi.login(any!)).thenThrow(Error()); await _bloc.login(email: 'test@test.com', password: '123456'); - verify(_mockSink.addError(any)).called(1); + verify(_mockSink.addError(any!)).called(1); }); test('creates session if api returns a LoginReponse', () async { const token = 'i am a token'; - when(_mockLoginApi.login(any)).thenAnswer( + when(_mockLoginApi.login(any!)).thenAnswer( (_) async => HttpEvent(data: LoginResponse(token))); await _bloc.login(email: 'test@test.com', password: '123456'); - verify(_mockSessionProvider.logUserIn(token, any)); + verify(_mockSessionProvider.logUserIn(token, any!)); }); test('gets contoller stream', () async { diff --git a/test/login_test.dart b/test/login_test.dart index c4e4e83..77d5034 100644 --- a/test/login_test.dart +++ b/test/login_test.dart @@ -18,15 +18,15 @@ import 'test_util/mocks.dart'; import 'test_util/test_util.dart'; void main() { - MockLoginBloc _mockLoginBloc; + MockLoginBloc? _mockLoginBloc; MockHomeBloc _mockHomeBloc; - MockNavigatorObserver _mockNavigationObserver; - Widget _testableWidget; - Finder _emailField; - Finder _passwordField; - Finder _submitButton; - Finder _passwordVisibilityToggle; - StreamController> _streamController; + MockNavigatorObserver? _mockNavigationObserver; + late Widget _testableWidget; + late Finder _emailField; + late Finder _passwordField; + late Finder _submitButton; + late Finder _passwordVisibilityToggle; + late StreamController> _streamController; setUp(() { _mockLoginBloc = MockLoginBloc(); @@ -41,7 +41,7 @@ void main() { dependencies: [ Provider(create: (_) => _mockHomeBloc), ], - navigatorObservers: [_mockNavigationObserver], + navigatorObservers: [_mockNavigationObserver], testingLocale: supportedLocales.first, ); @@ -50,7 +50,7 @@ void main() { _submitButton = find.byKey(Login.submitButtonKey); _passwordVisibilityToggle = find.byKey(Login.passwordVisibilityToggledKey); - when(_mockLoginBloc.stream).thenAnswer((_) => _streamController.stream); + when(_mockLoginBloc!.stream).thenAnswer((_) => _streamController.stream); }); group('attempts login', () { @@ -65,7 +65,7 @@ void main() { await tester.enterText(_passwordField, password); await tester.tap(_submitButton); - verify(_mockLoginBloc.login(email: email, password: password)); + verify(_mockLoginBloc!.login(email: email, password: password)); }); testWidgets('does not attempt login if email and password are empty', @@ -74,7 +74,7 @@ void main() { await tester.tap(_submitButton); - verifyNever(_mockLoginBloc.login(email: '', password: '')); + verifyNever(_mockLoginBloc!.login(email: '', password: '')); }); testWidgets('does not attempt login if email is not a valid email', @@ -88,7 +88,7 @@ void main() { await tester.enterText(_passwordField, password); await tester.tap(_submitButton); - verifyNever(_mockLoginBloc.login(email: email, password: password)); + verifyNever(_mockLoginBloc!.login(email: email, password: password)); }); testWidgets('does not attempt login if password is too short', @@ -102,7 +102,7 @@ void main() { await tester.enterText(_passwordField, password); await tester.tap(_submitButton); - verifyNever(_mockLoginBloc.login(email: email, password: password)); + verifyNever(_mockLoginBloc!.login(email: email, password: password)); }); }); @@ -226,7 +226,7 @@ void main() { await tester.pumpAndSettle(); - verify(_mockNavigationObserver.didPush(any, any)); + verify(_mockNavigationObserver!.didPush(any!, any)); expect(find.byType(Home), findsOneWidget); }); diff --git a/test/session_test.dart b/test/session_test.dart index fac8e6f..c1cfab6 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -6,8 +6,8 @@ import 'package:flutter_workshop/model/user/user.dart'; import 'test_util/mocks.dart'; void main() { - MockDiskStorageProvider _mockStorage; - Session _session; + late MockDiskStorageProvider _mockStorage; + late Session _session; setUp(() { _mockStorage = MockDiskStorageProvider(); @@ -24,7 +24,7 @@ void main() { // Given const token = 'i am a token'; when(_mockStorage.setAccessToken(any)).thenAnswer((_) async => true); - when(_mockStorage.setUser(any)).thenAnswer((_) async => true); + when(_mockStorage.setUser(any!)).thenAnswer((_) async => true); // When await _session.logUserIn(token, User.fake()); @@ -37,7 +37,7 @@ void main() { // Given final user = User.fake(); when(_mockStorage.setAccessToken(any)).thenAnswer((_) async => true); - when(_mockStorage.setUser(any)).thenAnswer((_) async => true); + when(_mockStorage.setUser(any!)).thenAnswer((_) async => true); // When await _session.logUserIn('any token', user); diff --git a/test/test_util/test_util.dart b/test/test_util/test_util.dart index 1e56363..a66d4b1 100644 --- a/test/test_util/test_util.dart +++ b/test/test_util/test_util.dart @@ -9,10 +9,10 @@ import 'package:provider/single_child_widget.dart'; class TestUtil { static Widget makeTestableWidget({ - Widget subject, - List dependencies, - List navigatorObservers, - Locale testingLocale, + Widget? subject, + required List dependencies, + List? navigatorObservers, + Locale? testingLocale, }) { return MultiProvider( providers: dependencies, @@ -25,25 +25,25 @@ class TestUtil { supportedLocales: supportedLocales, onGenerateRoute: getRouteFactory, home: subject, - navigatorObservers: navigatorObservers ?? const [], + navigatorObservers: navigatorObservers as List? ?? const [], ), ); } static Finder findInternationalizedText(String localizationKey) { - final Map localizationMap = Strings().map[localizationKey]; + final Map? localizationMap = Strings().map[localizationKey]; return find.byElementPredicate((Element candidate) { if (candidate.widget is Text) { - final Text textWidget = candidate.widget; + final Text textWidget = candidate.widget as Text; return (textWidget.data != null) - ? localizationMap.containsValue(textWidget.data) - : localizationMap.containsValue(textWidget.textSpan.toPlainText()); + ? localizationMap!.containsValue(textWidget.data) + : localizationMap!.containsValue(textWidget.textSpan!.toPlainText()); } if (candidate.widget is EditableText) { - final EditableText editable = candidate.widget; - return localizationMap.containsValue(editable.controller.text); + final EditableText editable = candidate.widget as EditableText; + return localizationMap!.containsValue(editable.controller.text); } return false; diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart index 1c9c66a..7f7eb6b 100644 --- a/test_driver/app_test.dart +++ b/test_driver/app_test.dart @@ -13,7 +13,7 @@ void main() { final passwordVisibilityToggleVisibleFinder = find.byValueKey(loginPasswordVisibilityToggleVisibleValueKey); - FlutterDriver driver; + FlutterDriver? driver; // Connect to the Flutter driver before running any tests. setUpAll(() async { @@ -23,26 +23,26 @@ void main() { // Close the connection to the driver after the tests have completed. tearDownAll(() async { if (driver != null) { - await driver.close(); + await driver!.close(); } }); test('password visibility toggle shows correct icon', () async { final homeLoginButtonFinder = find.byValueKey(homeLoginButtonValueKey); - await driver.tap(homeLoginButtonFinder); + await driver!.tap(homeLoginButtonFinder); - await driver.waitFor(passwordVisibilityToggleObscureFinder, + await driver!.waitFor(passwordVisibilityToggleObscureFinder, timeout: defaultWaitForTimeout); - await driver.tap(passwordVisibilityToggleFinder); + await driver!.tap(passwordVisibilityToggleFinder); - await driver.waitFor(passwordVisibilityToggleVisibleFinder, + await driver!.waitFor(passwordVisibilityToggleVisibleFinder, timeout: defaultWaitForTimeout); - await driver.tap(passwordVisibilityToggleFinder); + await driver!.tap(passwordVisibilityToggleFinder); - await driver.waitFor(passwordVisibilityToggleObscureFinder, + await driver!.waitFor(passwordVisibilityToggleObscureFinder, timeout: defaultWaitForTimeout); }); });