Flutter Authentication using bloc package: A complete Shopping App-Part 3

Sanjib Maharjan
11 min readJun 27, 2023

--

Welcome to the third part of the tutorial. We have completed the dashboard page and item detail page in our previous tutorials and If you want to check them out here are the links for part I and part II. In this section, we’ll work on the authentication module where the users can log in to the app. You can check out my tutorial on Login and Register page design for detail design tutorial with form validation.

Basic Flow

Users can log in to the application by using the valid credentials which are the mock credentials stored in the code itself and can be replaced later with the real business logic in the server. The login information is then stored in the app by using the “shared_preferences” library so that the user doesn't have to log in time and again. Some functionality of the app such as “Add to Cart” requires the user to log in and if users click “Add to cart” they are presented with a login popup if they are not logged in. We’ll implement the login screen in the form of a dialog popup.

Create a file “lib/ui/auth/login/login.dart” with the following contents:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_shopping_app/constant/color.dart';
import 'package:flutter_shopping_app/ui/common/form/custom_button.dart';
import 'package:flutter_shopping_app/ui/common/form/custom_text_field.dart';

class LoginForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 40, top: 20),
child: Text(
"Login to the app",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: ThemeTextColorLight),
),
),
Divider(
height: 0,
thickness: 16,
color: ThemeTextColorLightest,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: TextButton.icon(
icon: Icon(
Icons.login_rounded,
size: 50,
color: ThemeColor,
),
label: Text(
"SHOP",
style: TextStyle(fontSize: 30),
)),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
child: CustomTextField(
placeholder: "Username",
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: CustomTextField(
placeholder: "Password",
),
),
CustomButton(
label: "Login",
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 70),
),
SizedBox(
height: 20,
),
Text(
"Don't have an account? Sign up",
style: TextStyle(color: ThemeColor),
),
SizedBox(
height: 10,
),
Text(
"Forgot password?",
style: TextStyle(color: ThemeTextColorLight),
),
SizedBox(
height: 30,
),
],
),
),
),
Positioned(
child: IconButton(
icon: Icon(Icons.close), onPressed: () => _close(context)),
top: 10,
right: 10,
)
],
);
}

_close(BuildContext context) {
Navigator.of(context).pop();
}
}

Create a utility file “lib/util/dialog_util.dart” with the following contents:

import 'package:flutter/material.dart';
import 'package:flutter_shopping_app/ui/auth/login/login.dart';

class DialogUtil {
static const String SVG_ATTENTION =
'assets/images/svgs/dialogs/attention.svg';
static const String SVG_CONFIRM = 'assets/images/svgs/dialogs/confirm.svg';

static Future openLoginPopup(BuildContext context, {dismissible: false}) {
return showDialog(
context: context,
barrierDismissible: dismissible,
builder: (BuildContext dialogContext) {
return WillPopScope(
onWillPop: () async => dismissible,
child: Dialog(
insetPadding: EdgeInsets.all(10),
backgroundColor: Colors.transparent,
elevation: 0,
child: LoginForm()),
);
},
);
}
}

Create a custom drawer file “lib/ui/common/custom_drawer.dart” as:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_shopping_app/constant/color.dart';
import 'package:flutter_shopping_app/util/dialog_util.dart';

class CustomDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: [
_tile("Login", Icons.login_rounded, onPressed: () => _login(context)),
],
),
);
}

_login(BuildContext context) {
Navigator.of(context).pop();
DialogUtil.openLoginPopup(context);
}

Widget _tile(String title, IconData icon, {VoidCallback onPressed}) {
return Column(
children: [
ListTile(
dense: true,
title: Row(
children: [
Icon(
icon,
color: ThemeColor,
),
SizedBox(
width: 10,
),
Text(
title,
style: TextStyle(color: ThemeColor),
),
],
),
onTap: onPressed,
),
Divider(
height: 0,
thickness: 1,
color: ThemeColor,
)
],
);
}
}

And update the “lib/main.dart” file with the drawer we’ve just created as:

...home: Scaffold(
backgroundColor: Colors.white,
body: Dashboard(),
appBar: AppBar(
centerTitle: false,
title: Text("Shopping App"),
elevation: 0,
),
drawer: CustomDrawer(),
),
...

Next, create a model file “data/models/user/user_auth.dart” for storing user auth data as:

import 'package:json_annotation/json_annotation.dart';

part 'user_auth.g.dart';

@JsonSerializable()
class UserAuth {
final String username;
final int id;
final String assessToken;
final double refreshToken;
final String email;

UserAuth({
this.username,
this.assessToken,
this.refreshToken,
this.id,
this.email,
});

factory UserAuth.fromJson(Map<String, dynamic> json) =>
_$UserAuthFromJson(json);

Map<String, dynamic> toJson() => _$UserAuthToJson(this);

String toString() {
return '''{
"username": "$username",
}''';
}
}

Run: fvm flutter packages pub run build_runner build — delete-conflicting-outputs

Add the following dependencies in the “pubspec.yaml” file:

shared_preferences: ^2.0.5
bot_toast: ^4.0.1

We’ll use shared preferences to persist the user data to the device and bot toast to show error, info, and loading messages in the application.

The loading message may be blocking or non-blocking. The blocking loader restricts the user to access any component in the app as long as it is active while the non-blocking does not.

Create “util/pref_util.dart” file as:

import 'dart:convert';
import 'package:flutter_shopping_app/data/models/user/user_auth.dart';
import 'package:shared_preferences/shared_preferences.dart';

enum AUTH_STORE { isLoggedIn, userAuth }

abstract class PrefUtils {
///
/// Instantiation of the SharedPreferences library
///
static final Future<SharedPreferences> prefs =
SharedPreferences.getInstance();

static const String USER_IDENTIFIER = "tokens_";

static final Map<AUTH_STORE, dynamic> authStore = <AUTH_STORE, dynamic>{
AUTH_STORE.isLoggedIn: false,
};

static Map<AUTH_STORE, dynamic> getAuthStore() {
return authStore;
}

/// check if user is logged in or not
static bool isUserLoggedIn() {
return authStore[AUTH_STORE.isLoggedIn];
}

/// get [UserAuth]
static UserAuth getUserAuthData() {
return authStore[AUTH_STORE.userAuth];
}

static int getUserId() {
return authStore[AUTH_STORE]?.id;
}

/// load [UserAuth] if saved in shared pref
static Future<void> loadUserAuthData() async {
final userString = (await prefs).get(USER_IDENTIFIER) ?? null;
if (userString != null) {
final userJson = json.decode(userString);
updateUserSetting(
userAuthData: UserAuth.fromJson(userJson), isLoggedIn: true);
} else {
return null;
}
}

/// save [UserAuth] in shared pref
static Future<bool> storeUserAuthData(UserAuth userAuthData) async {
updateUserSetting(userAuthData: userAuthData, isLoggedIn: true);
// converts to Map<String, dynamic>
final userAuth = userAuthData.toJson();
// converts Map<String, dynamic> to string type using jsonEncode()
// and save in shared preference
return (await prefs).setString(USER_IDENTIFIER, jsonEncode(userAuth));
}

static Future<bool> clearUserToken() async {
updateUserSetting(clear: true);
return (await prefs).remove(USER_IDENTIFIER);
}

static void updateUserSetting(
{UserAuth userAuthData, bool isLoggedIn, bool clear: false}) async {
if (userAuthData != null || clear) {
authStore[AUTH_STORE.userAuth] = userAuthData;
}

if (isLoggedIn != null || clear) {
authStore[AUTH_STORE.isLoggedIn] = isLoggedIn ?? false;
}
}
}

Also, add the “bloc/token/auth_token_cubit.dart” and “bloc/token/auth_token_state.dart” files to update the login state in the view without having the user reload the application with the following contents:

File: “bloc/token/auth_token_cubit.dart

import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_app/data/models/user/user_auth.dart';
import 'package:flutter_shopping_app/util/pref_util.dart';
import 'package:injectable/injectable.dart';

part 'auth_token_state.dart';

@injectable
class AuthTokenCubit extends Cubit<AuthTokenState> {
AuthTokenCubit() : super(AuthTokenState.initial());

void update({UserAuth auth, bool isLoggedIn}) async {
try {
emit(state.update(
auth: auth ?? PrefUtil.getUserAuthData(),
isLoggedIn: isLoggedIn ?? PrefUtil.isUserLoggedIn()));
} catch (e) {
final errorMsg = e.toString();
emit(state.update(error: errorMsg));
}
}
}

File: “bloc/token/auth_token_state.dart

part of 'auth_token_cubit.dart';

@immutable
class AuthTokenState extends Equatable {
final UserAuth auth;
final String error;

final bool isLoggedIn;

bool get hasError => error != null && error.isNotEmpty;

const AuthTokenState({this.error, this.auth, this.isLoggedIn});

@override
List<Object> get props => [error, isLoggedIn, auth];

factory AuthTokenState.initial() {
return AuthTokenState(error: "", isLoggedIn: false);
}

AuthTokenState init({bool isLoggedIn, UserAuth auth, error}) {
return AuthTokenState(
auth: auth ?? this.auth,
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
error: error ?? "",
);
}

@override
String toString() =>
'AuthTokenState { auth: $auth, error: $error, isLoggedIn: $isLoggedIn }';
}

Run: fvm flutter packages pub run build_runner build — delete-conflicting-outputs

Similarly create the “util/message_util.dart” file as:

import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_shopping_app/constant/color.dart';

class MessageUtil {
static CancelFunc loadingCancelFunc;

static void _show(String message, {bool error}) {
BotToast.showCustomNotification(
toastBuilder: (context) =>
_CustomMessage(message: message, error: error),
duration: Duration(seconds: 4),
align: const Alignment(0, 0.99));
}

static void showSuccessMessage(String message) {
_show(message, error: false);
}

static void showErrorMessage(String message) {
_show(message, error: true);
}

static void showLoading({String message: 'loading...'}) {
if (loadingCancelFunc == null)
loadingCancelFunc = BotToast.showCustomLoading(
backgroundColor: Colors.black.withOpacity(.3),
toastBuilder: (context) => _LoaderOverlay(label: message));
}

static void hideLoading() {
if (loadingCancelFunc != null) {
loadingCancelFunc();
loadingCancelFunc = null;
}
}
}

class _LoaderOverlay extends StatelessWidget {
final String label;

_LoaderOverlay({this.label: "Please wait..."});

@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 300,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10)),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Please wait",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ThemeTextColorLight),
),
SizedBox(
height: 10,
),
Row(
children: [
SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(
strokeWidth: 2,
backgroundColor: ThemeTextColor,
)),
SizedBox(
width: 16,
),
Text(
label,
style: TextStyle(fontSize: 12, color: ThemeTextColor),
),
],
),
],
),
),
);
}
}

class _CustomMessage extends StatelessWidget {
final bool error;
final String message;

_CustomMessage({
@required this.message,
this.error: true,
});

@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: error ? Colors.redAccent : ThemeColor,
borderRadius: BorderRadius.circular(10)),
width: double.infinity,
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: Row(
children: [
Icon(error ? Icons.info : Icons.error),
SizedBox(width: 10),
Expanded(
child: Text(
message,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.white),
),
),
],
),
);
}
}

Finally, make changes in the “main.dart” file as:

import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_app/bloc/token/auth_token_cubit.dart';
import 'package:flutter_shopping_app/constant/color.dart';
import 'package:flutter_shopping_app/injectable/config.dart';
import 'package:flutter_shopping_app/ui/common/custom_drawer.dart';
import 'package:flutter_shopping_app/ui/dashboard/dashboard.dart';
import 'package:flutter_shopping_app/util/pref_util.dart';
import 'package:get_it/get_it.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
// load user stored data if any
await PrefUtil.loadUserAuthData();
configureDependencies();
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthTokenCubit>(
create: (context) => GetIt.I.get<AuthTokenCubit>()..init(),
lazy: false,
)
],
child: MaterialApp(
title: 'Shopping App Demo',
builder: BotToastInit(),
navigatorObservers: [BotToastNavigatorObserver()],
//set up bot toast
theme: ThemeData(
primarySwatch: ThemeWhite,
),
home: Scaffold(
backgroundColor: Colors.white,
body: Dashboard(),
appBar: AppBar(
centerTitle: false,
title: Text("Shopping App"),
elevation: 0,
),
drawer: CustomDrawer(),
),
),
);
}
}

Now let's create some fake users in our MockUtil class and we’ll use these values to mock our login function. Add following entries in the “util/mock_util.dart” file as:

...
static List<Map<String, dynamic>> getUsers() {
return [
{"username": "user1", "password": "password1", "id": 1, "email": "user1@email.com"},
{"username": "user2", "password": "password2", "id": 2, "email": "user2@email.com"},
{"username": "user3", "password": "password3", "id": 3, "email": "user3@email.com"},
{"username": "user4", "password": "password4", "id": 4, "email": "user4@email.com"},
];
}
...

Then add the logic in the file “data/provider/user_provider.dart” as:

import 'package:flutter_shopping_app/data/models/user/user_auth.dart';
import 'package:flutter_shopping_app/util/mock_util.dart';
import 'package:injectable/injectable.dart';

abstract class UserProvider {
Future<UserAuth> login(String username, String password);
}

@Named("mock")
@Singleton(as: UserProvider)
class MockUserProvider implements UserProvider {
const MockUserProvider();

@override
Future<UserAuth> login(String username, String password) async {
//add some delay to give the feel of api call
await Future.delayed(Duration(seconds: 3));
final List<Map<String, dynamic>> _data = MockUtil.getUsers();
Map<String, dynamic> _user = _data.firstWhere(
(Map data) =>
data["username"]?.toString()?.trim() == username &&
data["password"]?.toString()?.trim() == password,
orElse: () => null);
if (_user == null) throw Exception("Username and password does not match");

return Future.value(UserAuth.fromJson(_user));
}
}

Also, create the corresponding repository file “data/repository/user_repository.dart” as:

import 'package:flutter_shopping_app/data/models/user/user_auth.dart';
import 'package:flutter_shopping_app/data/provider/user_provider.dart';
import 'package:injectable/injectable.dart';

@injectable
class UserRepository {
final UserProvider _provider;

const UserRepository({@Named("mock") UserProvider provider})
: _provider = provider;

Future<UserAuth> handleLogin(String username, String password) async {
return await _provider.login(username, password);
}
}

We also need a bloc to communicate the login form with the API call. The user types in the username and password in the form which the app extracts and checks if they are valid. If it is valid then the user is logged in successfully and if not some error message is shown to the user.

Create the files “ui/auth/login/bloc/login_cubit.dart” and “ui/auth/login/bloc/login_state.dart” as:

import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_app/bloc/token/auth_token_cubit.dart';
import 'package:flutter_shopping_app/data/models/user/user_auth.dart';
import 'package:flutter_shopping_app/data/repository/user_repository.dart';
import 'package:flutter_shopping_app/util/message_util.dart';
import 'package:flutter_shopping_app/util/pref_util.dart';
import 'package:injectable/injectable.dart';

part 'login_state.dart';

@injectable
class LoginCubit extends Cubit<LoginState> {
final UserRepository _userRepository;
final AuthTokenCubit _authTokenCubit;

LoginCubit(
{UserRepository userRepository,
@factoryParam AuthTokenCubit authTokenCubit})
: _userRepository = userRepository,
_authTokenCubit = authTokenCubit,
super(LoginState.initial());

void login(String userName, String password) async {
emit(state.update(loading: true));
try {
MessageUtil.showLoading();
emit(state.update(loading: true));
UserAuth _auth = await _userRepository.handleLogin(userName, password);
await PrefUtil.storeUserAuthData(_auth);
_authTokenCubit.init(auth: _auth, isLoggedIn: true);
emit(state.update(success: true, loading: false));
MessageUtil.showSuccessMessage("Login Successful.");
} catch (e) {
final String _error = e.toString() ?? "Error";
MessageUtil.showErrorMessage(_error);
emit(state.update(error: _error, loading: false));
}finally{
MessageUtil.hideLoading();
}
}
}

File: “ui/auth/login/bloc/login_state.dart”

part of 'login_cubit.dart';

@immutable
class LoginState extends Equatable {
final bool loading;

final String error;
final bool success;

bool get hasError => error.isNotEmpty;

LoginState({this.loading, this.error, this.success});

factory LoginState.initial() => LoginState(
loading: false,
success: false,
error: "",
);

LoginState update({bool loading, bool success, String error}) {
return LoginState(
loading: loading ?? this.loading,
success: success ?? this.success,
error: error ?? "",
);
}

@override
List<Object> get props => [loading, hasError, success];
}

Then update the file “ui/auth/login/login.dart” as:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_app/bloc/token/auth_token_cubit.dart';
import 'package:flutter_shopping_app/constant/color.dart';
import 'package:flutter_shopping_app/ui/auth/login/bloc/login_cubit.dart';
import 'package:flutter_shopping_app/ui/common/form/custom_button.dart';
import 'package:flutter_shopping_app/ui/common/form/custom_text_field.dart';
import 'package:get_it/get_it.dart';

class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
bool _isVisible = false;

final _userData = {"username": "", "password": ""};

@override
void initState() {
_usernameController
.addListener(() => _setValue("username", _usernameController));
_passwordController
.addListener(() => _setValue("password", _passwordController));
super.initState();
}

@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}

_setValue(key, TextEditingController controller) {
if (_userData[key] != controller.text.trim()) {
setState(() {
_userData[key] = controller.text.trim();
});
}
}

_listenEvents(BuildContext context, LoginState state) {
if (state.success) {
_close(context);
}
}

@override
Widget build(BuildContext context) {
return BlocProvider<LoginCubit>(
create: (context) =>
GetIt.I.get<LoginCubit>(param1: context.read<AuthTokenCubit>()),
child: BlocListener<LoginCubit, LoginState>(
listener: _listenEvents,
child: BlocBuilder<LoginCubit, LoginState>(builder: (context, state) {
return Stack(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 40, top: 20),
child: Text(
"Login to the app",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: ThemeTextColorLight),
),
),
Divider(
height: 0,
thickness: 16,
color: ThemeTextColorLightest,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: TextButton.icon(
icon: Icon(
Icons.login_rounded,
size: 50,
color: ThemeColor,
),
label: Text(
"SHOP",
style: TextStyle(fontSize: 30),
)),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
child: CustomTextField(
placeholder: "Username",
enabled: !state.loading,
controller: _usernameController,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: CustomTextField(
placeholder: "Password",
enabled: !state.loading,
controller: _passwordController,
obscureText: !_isVisible,
suffixIcon: IconButton(
icon: Icon(_isVisible
? Icons.visibility
: Icons.visibility_off, color: ThemeColor,),
onPressed: _toggleVisibility,
),
),
),
CustomButton(
label: "Login",
padding:
EdgeInsets.symmetric(vertical: 20, horizontal: 70),
onPressed: () => _login(context),
disabled: state.loading ||
!_isUsernameValid() ||
!_isPasswordValid(),
),
SizedBox(
height: 20,
),
Text(
"Don't have an account? Sign up",
style: TextStyle(color: ThemeColor),
),
SizedBox(
height: 10,
),
Text(
"Forgot password?",
style: TextStyle(color: ThemeTextColorLight),
),
SizedBox(
height: 30,
),
],
),
),
),
Positioned(
child: IconButton(
icon: Icon(Icons.close), onPressed: () => _close(context)),
top: 10,
right: 10,
)
],
);
}),
),
);
}

_toggleVisibility() {
setState(() {
_isVisible = !_isVisible;
});
}

_login(BuildContext context) {
context
.read<LoginCubit>()
.login(_userData["username"], _userData["password"]);
}

bool _isUsernameValid() {
return _userData["username"].isNotEmpty;
}

bool _isPasswordValid() {
return _userData["password"].isNotEmpty;
}

_close(BuildContext context) {
Navigator.of(context).pop();
}
}

Also, we need some way for the users to log out from the system and we’ll add it in the “ui/common/custom_drawer.dart” file as:

...
return Drawer(
child: ListView(
children: [
_tile(PrefUtil.isUserLoggedIn() ? "Logout" : "Login",
Icons.login_rounded,
onPressed: () => _handleLogin(context)),
],
),
);
}

_handleLogin(BuildContext context) {
Navigator.of(context).pop();
if (PrefUtil.isUserLoggedIn()) {
context.read<AuthTokenCubit>().logout();
} else {
DialogUtil.openLoginPopup(context);
}
}
...

To test the auth module create the “ui/dashboard/user_info.dart” file with the following contents:

import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_shopping_app/bloc/token/auth_token_cubit.dart';
import 'package:flutter_shopping_app/constant/color.dart';

class UserInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthTokenCubit, AuthTokenState>(
builder: (context, state) {
return state.isLoggedIn
? Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Hello ${state.auth.username},",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: ThemeTextColor),
),
SizedBox(height: 4,),
Text(
"your daily goods are here!",
style: TextStyle(fontSize: 22, color: ThemeTextColorLight),
),
SizedBox(height: 10,)
],
),
)
: SizedBox.shrink();
});
}
}

Also, add them in the dashboard as:

...
@override
Widget build(BuildContext context) {
return ListView(
children: [
UserInfo(),
...

We are done with the authentication module and can reuse this module wherever necessary. Our app works both in mobile as well as in web platforms while UI looks better only on a mobile platform. So in the next part, we’ll work to make the UI better on both mobile and web platforms.

Project Starter Link: https://github.com/cshanjib/flutter_shopping_app/tree/section3-start
Project Completed Link: https://github.com/cshanjib/flutter_shopping_app/tree/section3-end

Prev —Part 2: API calls and Dependency Injection using the get_it package
Next —Part 4: Responsive Design

Check out other tutorials in this series:

Part 1: Designing the E-commerce App
Part 2: API calls and Dependency Injection using the get_it package
Part 3: Authentication and Login Design using the flutter_bloc package
Part 4: Responsive Design
Part 5: Proper Routing in Flutter Web using beamer package
Part 6: Pagination in Flutter(Lazy Loading, Paginated Truncated Display, etc.)

If you like this tutorial please hit 👏 as many times as you like and also leave comments if you have any questions or suggestions. Thank you!

#flutter, #flutter_app_development, #ecommerce_application, #flutter_get_it #flutter_bloc #login_form_design

--

--