Skip to content

Commit

Permalink
Replace SignInModel & ChangeNotifier with ValueNotifier<bool> (#5)
Browse files Browse the repository at this point in the history
* Replace SignInModel with ChangeNotifier with ValueNotifier<bool>

* Inject ValueNotifier into manager and widget

* Move ValueListenableBuilder outside SignInPage

* Fix tests
  • Loading branch information
bizz84 committed May 16, 2019
1 parent 0432747 commit 0042329
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 87 deletions.
31 changes: 11 additions & 20 deletions lib/app/sign_in/sign_in_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,31 @@ import 'package:firebase_auth_demo_flutter/services/auth_service.dart';
import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';

class SignInModel with ChangeNotifier {
bool _loading = false;

bool get loading => _loading;
set loading(bool newValue) {
_loading = newValue;
notifyListeners();
}
}

class SignInManager {
SignInManager({@required this.auth});
SignInManager({@required this.auth, @required this.isLoading});
final AuthService auth;
final ValueNotifier<bool> isLoading;

Future<User> _signIn(SignInModel model, Future<User> Function() signInMethod) async {
Future<User> _signIn(Future<User> Function() signInMethod) async {
try {
model.loading = true;
isLoading.value = true;
return await signInMethod();
} catch (e) {
rethrow;
} finally {
model.loading = false;
isLoading.value = false;
}
}

Future<User> signInAnonymously(SignInModel model) async {
return await _signIn(model, auth.signInAnonymously);
Future<User> signInAnonymously() async {
return await _signIn(auth.signInAnonymously);
}

Future<void> signInWithGoogle(SignInModel model) async {
return await _signIn(model, auth.signInWithGoogle);
Future<void> signInWithGoogle() async {
return await _signIn(auth.signInWithGoogle);
}

Future<void> signInWithFacebook(SignInModel model) async {
return await _signIn(model, auth.signInWithFacebook);
Future<void> signInWithFacebook() async {
return await _signIn(auth.signInWithFacebook);
}
}
83 changes: 47 additions & 36 deletions lib/app/sign_in/sign_in_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,39 @@ import 'package:firebase_auth_demo_flutter/app/sign_in/social_sign_in_button.dar
import 'package:firebase_auth_demo_flutter/common_widgets/platform_exception_alert_dialog.dart';
import 'package:firebase_auth_demo_flutter/constants/strings.dart';
import 'package:firebase_auth_demo_flutter/services/auth_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';


class SignInPage extends StatelessWidget {
const SignInPage._({Key key, this.manager, this.title}) : super(key: key);
SignInPage._({Key key, this.isLoading, this.manager, this.title}) : super(key: key);
final SignInManager manager;
final String title;
final bool isLoading;

// P<ValueNotifier>
// P<SignInManager>(valueNotifier)
// ValueListenableBuilder(valueListener)
// SignInPage(value)
static Widget create(BuildContext context) {
final AuthService auth = Provider.of<AuthService>(context, listen: false);
return Provider<SignInManager>(
builder: (BuildContext context) => SignInManager(auth: auth),
child: Consumer<SignInManager>(
builder: (BuildContext context, SignInManager manager, _) => SignInPage._(
manager: manager,
title: 'Firebase Auth Demo',
return Provider<ValueNotifier<bool>>(
builder: (BuildContext context) => ValueNotifier<bool>(false),
child: Consumer<ValueNotifier<bool>>(
builder: (BuildContext context, ValueNotifier<bool> isLoading, _) => Provider<SignInManager>(
builder: (BuildContext context) => SignInManager(auth: auth, isLoading: isLoading),
child: Consumer<SignInManager>(
builder: (BuildContext context, SignInManager manager, _) => ValueListenableBuilder<bool>(
valueListenable: isLoading,
builder: (BuildContext context, bool isLoading, Widget child) => SignInPage._(
isLoading: isLoading,
manager: manager,
title: 'Firebase Auth Demo',
),
),
),
),
),
);
Expand All @@ -34,27 +50,27 @@ class SignInPage extends StatelessWidget {
).show(context);
}

Future<void> _signInAnonymously(BuildContext context, SignInModel model) async {
Future<void> _signInAnonymously(BuildContext context) async {
try {
await manager.signInAnonymously(model);
await manager.signInAnonymously();
} on PlatformException catch (e) {
_showSignInError(context, e);
}
}

Future<void> _signInWithGoogle(BuildContext context, SignInModel model) async {
Future<void> _signInWithGoogle(BuildContext context) async {
try {
await manager.signInWithGoogle(model);
await manager.signInWithGoogle();
} on PlatformException catch (e) {
if (e.code != 'ERROR_ABORTED_BY_USER') {
_showSignInError(context, e);
}
}
}

Future<void> _signInWithFacebook(BuildContext context, SignInModel model) async {
Future<void> _signInWithFacebook(BuildContext context) async {
try {
await manager.signInWithFacebook(model);
await manager.signInWithFacebook();
} on PlatformException catch (e) {
if (e.code != 'ERROR_ABORTED_BY_USER') {
_showSignInError(context, e);
Expand All @@ -73,26 +89,21 @@ class SignInPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<SignInModel>(
builder: (BuildContext context) => SignInModel(),
child: Consumer<SignInModel>(builder: (BuildContext context, SignInModel model, _) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(title),
),
// Hide developer menu while loading in progress.
// This is so that it's not possible to switch auth service while a request is in progress
drawer: model.loading ? null : DeveloperMenu(),
backgroundColor: Colors.grey[200],
body: _buildSignIn(context, model),
);
}),
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(title),
),
// Hide developer menu while loading in progress.
// This is so that it's not possible to switch auth service while a request is in progress
drawer: isLoading ? null : DeveloperMenu(),
backgroundColor: Colors.grey[200],
body: _buildSignIn(context),
);
}

Widget _buildHeader(SignInModel model) {
if (model.loading) {
Widget _buildHeader() {
if (isLoading) {
return Center(
child: CircularProgressIndicator(),
);
Expand All @@ -104,7 +115,7 @@ class SignInPage extends StatelessWidget {
);
}

Widget _buildSignIn(BuildContext context, SignInModel model) {
Widget _buildSignIn(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.0),
child: Column(
Expand All @@ -113,27 +124,27 @@ class SignInPage extends StatelessWidget {
children: <Widget>[
SizedBox(
height: 50.0,
child: _buildHeader(model),
child: _buildHeader(),
),
SizedBox(height: 48.0),
SocialSignInButton(
assetName: 'assets/go-logo.png',
text: Strings.signInWithGoogle,
onPressed: model.loading ? null : () => _signInWithGoogle(context, model),
onPressed: isLoading ? null : () => _signInWithGoogle(context),
color: Colors.white,
),
SizedBox(height: 8),
SocialSignInButton(
assetName: 'assets/fb-logo.png',
text: Strings.signInWithFacebook,
textColor: Colors.white,
onPressed: model.loading ? null : () => _signInWithFacebook(context, model),
onPressed: isLoading ? null : () => _signInWithFacebook(context),
color: Color(0xFF334D92),
),
SizedBox(height: 8),
SignInButton(
text: Strings.signInWithEmail,
onPressed: model.loading ? null : () => _signInWithEmail(context),
onPressed: isLoading ? null : () => _signInWithEmail(context),
textColor: Colors.white,
color: Colors.teal[700],
),
Expand All @@ -148,7 +159,7 @@ class SignInPage extends StatelessWidget {
text: Strings.goAnonymous,
color: Colors.lime[300],
textColor: Colors.black,
onPressed: model.loading ? null : () => _signInAnonymously(context, model),
onPressed: isLoading ? null : () => _signInAnonymously(context),
),
],
),
Expand Down
1 change: 0 additions & 1 deletion test/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ import 'package:mockito/mockito.dart';

class MockAuthService extends Mock implements AuthService {}


60 changes: 30 additions & 30 deletions test/sign_in_bloc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@ import 'dart:async';

import 'package:firebase_auth_demo_flutter/app/sign_in/sign_in_manager.dart';
import 'package:firebase_auth_demo_flutter/services/auth_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

import 'mocks.dart';

class MockValueNotifier<T> extends ValueNotifier<T> {
MockValueNotifier(T value) : super(value);

List<T> values = <T>[];

@override
set value(T newValue) {
values.add(newValue);
super.value = newValue;
}
}

void main() {
MockAuthService mockAuthService;
SignInManager bloc;
SignInManager manager;
MockValueNotifier<bool> isLoading;

setUp(() {
mockAuthService = MockAuthService();
bloc = SignInManager(auth: mockAuthService);
isLoading = MockValueNotifier<bool>(false);
manager = SignInManager(auth: mockAuthService, isLoading: isLoading);
});

tearDown(() {
mockAuthService = null;
bloc = null;
manager = null;
isLoading = null;
});

void stubSignInAnonymouslyReturnsUser() {
Expand All @@ -31,42 +47,26 @@ void main() {
}

test(
'WHEN bloc signs in anonymously'
'WHEN manager signs in anonymously'
'AND auth returns valid user'
'THEN loading stream emits true, false', () async {
'THEN isLoading values are [ true, false ]', () async {
stubSignInAnonymouslyReturnsUser();

await bloc.signInAnonymously();

expect(
bloc.isLoadingStream,
emitsInOrder(
<bool>[
true,
false,
],
),
);
await manager.signInAnonymously();

expect(isLoading.values, <bool>[true, false]);
});

test(
'WHEN bloc signs in anonymously'
'WHEN manager signs in anonymously'
'AND auth throws an exception'
'THEN bloc throws an exception'
'AND loading stream emits true, false', () async {
'THEN manager throws an exception'
'THEN isLoading values are [ true, false ]', () async {
final exception = PlatformException(code: 'ERROR_MISSING_PERMISSIONS');
stubSignInAnonymouslyThrows(exception);

expect(() async => await bloc.signInAnonymously(), throwsA(exception));

expect(
bloc.isLoadingStream,
emitsInOrder(
<bool>[
true,
false,
],
),
);
expect(() async => await manager.signInAnonymously(), throwsA(exception));

expect(isLoading.values, <bool>[true, false]);
});
}

0 comments on commit 0042329

Please sign in to comment.