Skip to content

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 assets
  • themes application theme defines here
  • utils refers to a set of utility functions or classes that are used to perform common tasks
  • values type data hard code
  • routes management navigation with autoroute
  • widgets supporting component
  • bloc state management folder
  • pages 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.

sl.registerFactory(() => UserCubit(userGetData: sl(), networkInfo: sl()));

For more Information please learn local package doman and injectable.

Authors: Nanang Prasetya