Feature Layer¶
The Feature Layer contains the application's core business logic, such as the rules and logic that govern the application's domain, as well as the user interface, such as the presentation of data to the user. The Feature Layer is responsible for handling user interactions, such as user input, and for displaying the results of the application's business logic to the user.
In the folder Feature layer has path core
. To follow this step you have to understand :
Structure
extensions
a piece of code that adds functionality to an existing class or library, without modifying the original source code.languages
for language assetsthemes
application theme defines hereutils
refers to a set of utility functions or classes that are used to perform common tasksvalues
type data hard coderoutes
management navigation withautoroute
widgets
supporting componentbloc
state management folderpages
ui presenntaion
1. Create UI design¶
Creating UI designs in Flutter involves using the widgets provided by the framework to build and layout the visual elements of the app.
class UserPage extends StatefulWidget {
final PageStorageBucket bucket;
const UserPage({Key? key, required this.bucket}) : super(key: key);
@override
State<UserPage> createState() => UserPageState();
}
class UserPageState extends State<UserPage> {
late RefreshController _refreshController = RefreshController();
late ScrollController _scrollController = ScrollController(
initialScrollOffset: widget.bucket.currentPageScrollOffset(context, KEY_USERS),
);
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.infinityBottom()) context.read<UserCubit>().getAllData();
});
}
@override
Widget build(BuildContext context) {
return BlocBuilder<UserDetailCubit, UserDetailState>(
builder: (ctx, state) {
return ScaffoldResponsive(
sideBar: state.isInit ? null : UserDetailPage(userEntity: state.userEntity),
body: _contentBuilder(ctx, state),
);
},
);
}
Widget _contentBuilder(BuildContext ctxDtl, UserDetailState stateDtl) {
return NotificationListener<ScrollNotification>(
onNotification: (pos) => widget.bucket.saveScrollOffset(context, pos: pos, key: KEY_USERS),
child: BlocConsumer<UserCubit, UserState>(
listener: (context, state) {
if (state.message != null) {
Fluttertoast.showToast(msg: state.message!, toastLength: Toast.LENGTH_SHORT);
}
},
builder: (context, state) {
if (state is UserLoaded)
return SmartRefresher(
controller: _refreshController,
onRefresh: () {
context.read<UserCubit>().refreshUsers();
_refreshController.refreshCompleted();
},
child: ListView.separated(
controller: _scrollController,
padding: const EdgeInsets.all(AppDimens.radiusMedium),
itemCount: state.hasMax ? state.data.length : state.data.length + 1,
itemBuilder: (_, index) => index >= state.data.length
? const BottomLoader()
: ListTile(
onTap: () {},
leading: Text('${state.data[index].id}'),
title: Text(state.data[index].name),
subtitle: Text(state.data[index].phone),
selectedColor: AppColors.blackText,
selectedTileColor: AppColors.lightOrange2,
),
separatorBuilder: (_, index) => Divider(),
),
);
else if (state is UserNotLoaded)
return FailureView(
onPressed: () => context.read<UserCubit>().getAllData(),
message: state.message,
);
else
return ListLoader();
},
),
);
}
}
2. Create State management¶
For this step we use the package Bloc State management.
Cubit is a state management library for Flutter that uses the concept of "cubits" to manage the state of an app. A cubit is a simple, immutable class that holds the state of a specific part of the app and emits new state when it changes.
Cubit¶
class UserCubit extends Cubit<UserState> {
final UserGetData userGetData;
final NetworkInfo networkInfo;
UserCubit({required this.userGetData, required this.networkInfo}) : super(UserInitial());
void getAllData() async {
bool isConnected = await networkInfo.isConnected;
if (state.hasMax) return;
if (state is UserInitial) emit(UserLoading());
if (state is UserLoading || state is UserNotLoaded) return refreshUsers();
Either<Failure, List<UserEntity>> data = await userGetData.call(UserParamsEntity(
start: (state as UserLoaded).data.length,
isInit: false,
));
data.fold(
(failure) => emit(UserNotLoaded(message: failure.message!)),
(value) => emit(UserLoaded(
data: isConnected ? [...(state as UserLoaded).data, ...value] : value,
hasMax: value.isEmpty || !isConnected,
)),
);
}
Future<void> refreshUsers() async {
bool isConnected = await networkInfo.isConnected;
Either<Failure, List<UserEntity>> data = await userGetData.call(UserParamsEntity());
data.fold(
(failure) => emit(UserNotLoaded(message: failure.message!)),
(value) => emit(UserLoaded(
data: value,
hasMax: value.length < UserParamsEntity().limit || !isConnected,
)),
);
}
}
State¶
abstract class UserState extends Equatable {
final String? message;
final bool hasMax;
UserState(this.message, this.hasMax);
@override
List<Object?> get props => [message];
}
class UserInitial extends UserState {
UserInitial() : super(null, false);
}
class UserLoading extends UserState {
UserLoading() : super(null, false);
}
class UserLoaded extends UserState {
final List<UserEntity> data;
final bool hasMax;
final String? message;
UserLoaded({
required this.data,
required this.hasMax,
this.message,
}) : super(message, hasMax);
@override
List<Object?> get props => [data, hasMax, message];
}
class UserNotLoaded extends UserState {
final String message;
UserNotLoaded({
required this.message,
}) : super(message, false);
@override
List<Object?> get props => [message];
}
3. Register to Dependancy Injection¶
For UserGetData
and NetworkInfo
put on local package domain
. Dont forget to register on injection.dart
.
For more Information please learn local package doman and injectable.