Morpheme Base
Link: pub.dev
MorphemeStatePage dan MorphemeCubit (State Management)
To use morpheme base you can use StatefullWidget
and in the class extends State
add the mixin with MorphemeStatePage<T extends StatefullWidget, C extends MorphemeCubit>
.
Methods that need to be overridden setCubit
and buildWidget
. the build
method is deprecated when using with MorphemeStatepage
and is replaced by buildWidget
.
Example
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:onboarding/widgets/widgets.dart';
import '../cubit/onboarding_cubit.dart';
class OnboardingPage extends StatefulWidget {
const OnboardingPage({Key? key}) : super(key: key);
State<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage>
with MorphemeStatePage<OnboardingPage, OnboardingCubit> {
OnboardingCubit setCubit() => locator();
Widget buildWidget(BuildContext context) {
return Scaffold(
body: Stack(
children: [
PageView(
controller: cubit.pageController,
onPageChanged: cubit.onPageChange,
children: cubit.listOnboarding,
),
const Positioned(
left: MorphemeSizes.s16,
right: MorphemeSizes.s16,
bottom: MorphemeSizes.s16,
child: OnboardingButton(),
),
],
),
);
}
}
To use MorphemeCubit
first we need State
from the data class which added the copyWith
method to replace the variables in State
.
part of 'onboarding_cubit.dart';
class OnboardingStateCubit extends Equatable {
const OnboardingStateCubit({
required this.selected,
required this.isLast,
});
final int selected;
final bool isLast;
OnboardingStateCubit copyWith({
int? selected,
bool? isLast,
}) {
return OnboardingStateCubit(
selected: selected ?? this.selected,
isLast: isLast ?? this.isLast,
);
}
List<Object?> get props => [selected, isLast]; // add all variables to list props
}
MorphemeCubit
is a controller for all the logic that will be used, by creating a class with extends MorphemeCubit<State>
in the constructor must call super(State())
to give the initial value to State
.
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import '../widgets/onboarding.dart';
part 'onboarding_state.dart';
class OnboardingCubit extends MorphemeCubit<OnboardingStateCubit> {
OnboardingCubit()
: super(const OnboardingStateCubit( // must call super with value initial state
selected: 0,
isLast: false,
));
...
void initState(BuildContext context) async {}
void initAfterFirstLayout(BuildContext context) {}
List<BlocProvider> blocProviders(BuildContext context) => [];
List<BlocListener> blocListeners(BuildContext context) => [];
void dispose() {}
void onPageChange(int value) => emit(state.copyWith(
selected: value,
isLast: value == listOnboarding.length - 1,
));
...
}
here's an example of using MorphemeCubit
with bloc
to fetch data to api on login_cubit.dart
.
import 'package:core/core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../data/models/body/login_body.dart';
import '../bloc/login/login_bloc.dart';
part 'login_state.dart';
class LoginCubit extends MorphemeCubit<LoginStateCubit> {
LoginCubit({required this.loginBloc}) : super(LoginStateCubit());
final LoginBloc loginBloc;
...
List<BlocProvider> blocProviders(BuildContext context) => [
BlocProvider<LoginBloc>(create: (context) => loginBloc),
];
List<BlocListener> blocListeners(BuildContext context) => [
BlocListener<LoginBloc, LoginState>(listener: _listenerLogin),
];
void dispose() {
try {
loginBloc.close();
} catch (e) {
if (kDebugMode) print(e.toString());
}
}
...
void onLoginWithEmailPressed(BuildContext context) {
_setValidate();
if (_isValidEmailPassword()) {
_fetchLogin(LoginBody(email: emailKey.text, password: passwordKey.text));
}
}
...
void _fetchLogin(LoginBody body) {
loginBloc.add(FetchLogin(body));
}
void _listenerLogin(BuildContext context, LoginState state) {
if (state is LoginFailed) {
state.failure.showSnackbar(context);
} else if (state is LoginSuccess) {
context.go(MorphemeRoutes.main);
}
}
}
initState
: same method onStatefullWidget
when doinginitState
.initAfterFirstLayout
: method that is called when the application finishes rendering what is in thebuild
widget.blocProviders
: initializebloc
to be used in this method.blocListeners
: catch callback ofstate bloc
which will be listened whenstate bloc
moves to anotherstate
.dispose
: same method onStatefullWidget
when doingdispose
.emit
: method used to changestate
andreactive
change UI that requiresstate
.
for pages that need reactive data from MorphemeCubit
we can do the extensions context.select
and context.watch
and call them in build
. then as for context.read
is used to call methods that are in MorphemeCubit
and are not listeners, for complete documentation it is in bloclibrary.dev.
context.select
: listen for changes to a selected variable fromMorphemeCubit
or fromState MorphemeCubit
.
// fetch data from OnboardingCubit
final listOnboarding = context.select((OnboardingCubit element) => element.listOnboarding);
// fetch data from OnboardingCubit state
final selected = context.select((OnboardingCubit element) => element.state.selected);
context.watch
: listen forState
changes, usually used to listen forState
fetch API changes starting fromInitial
,Loading
,Succss
&Failed
.
final watchLoginState = context.watch<LoginBloc>().state;
...
MorphemeButton.elevated(
isLoading: watchLoginState is LoginLoading,
text: context.s.loginWithEmail,
onPressed: () =>
context.read<LoginCubit>().onLoginWithEmailPressed(context),
)
context.read
: usually used for calling methods inMorphemeCubit
.
MorphemeButton.elevated(
isLoading: watchLoginState is LoginLoading,
text: context.s.loginWithEmail,
onPressed: () =>
context.read<LoginCubit>().onLoginWithEmailPressed(context),
)
and the following is an example of implementing state management
on onboarding_button.dart
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:onboarding/cubit/onboarding_cubit.dart';
import 'package:onboarding/widgets/indicator.dart';
class OnboardingButton extends StatelessWidget {
const OnboardingButton({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final listOnboarding =
context.select((OnboardingCubit element) => element.listOnboarding);
final selected =
context.select((OnboardingCubit element) => element.state.selected);
final isLast =
context.select((OnboardingCubit element) => element.state.isLast);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
isLast
? const SizedBox(width: 100)
: MorphemeButton.text(
isExpand: false,
text: context.s.skip,
style: TextButton.styleFrom(
minimumSize: const Size.fromWidth(100),
),
onPressed: context.read<OnboardingCubit>().onSkipPressed,
),
Indicator(length: listOnboarding.length, selected: selected),
MorphemeButton.elevated(
text: isLast ? context.s.started : context.s.next,
isExpand: false,
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromWidth(100),
),
onPressed: () =>
context.read<OnboardingCubit>().onNextPressed(context),
),
],
);
}
}