Efficient Builder
An Enhanced Alternative to ValueListenableBuilder
Efficient Builder offers a more robust alternative to Flutter's ValueListenableBuilder, enhancing how widgets react to state changes. It overcomes common limitations associated with ValueNotifier by implementing a separation technique that extracts states from the ViewModel into a distinct class. Additionally, it resolves issues related to unnecessary rebuilding and state changes, providing more granular control over UI states.
Features
-
Controlled Widget Rebuilds
- Fine-grained control over which widgets rebuild and when
- Optimize performance by preventing unnecessary rebuilds
-
Clean Architecture Support
- Separate state definitions from ViewModel logic
- Maintain clear boundaries between UI, state, and business logic
-
Simple Integration
- Minimal setup required
- Works seamlessly with existing Flutter projects
-
Type-safe State Management
- Full type safety for your states
- Compile-time error catching
Getting started
1.Add the package to your pubspec.yaml:
dependencies:
efficient_builder: ^1.0.2
2.Import it in your code:
import 'package:efficient_builder/efficient_builder.dart';
Usage
Define Your State
class LoginStates {
final String email;
final String password;
final bool showButton;
final ValidationError? emailError;
final ValidationError? passwordError;
LoginStates({
required this.email,
required this.password,
required this.showButton,
this.emailError,
this.passwordError,
});
factory LoginStates.initial() {
return LoginStates(
email: '',
password: '',
showButton: false,
);
}
LoginStates copyWith({
String? email,
String? password,
bool? showButton,
ValidationError? emailError,
ValidationError? passwordError,
}) {
return LoginStates(
email: email ?? this.email,
password: password ?? this.password,
showButton: showButton ?? this.showButton,
emailError: emailError ?? this.emailError,
passwordError: passwordError ?? this.passwordError,
);
}
}
Make ViewModel
class LoginViewModel {
final _loginStates = ValueNotifier<LoginStates>(LoginStates.initial());
LoginStates get _states => _loginStates.value;
ValueListenable<LoginStates> get loginStates => _loginStates;
void onChangedEmail({required String? email}) {
final emailError = EmailValidator.getEmailValidation(email);
_loginStates.value = _states.copyWith(
emailError: emailError,
email: email,
);
_checkUpdateButtonState();
}
void onChangedPassword({required String? password}) {
final passwordError = PasswordValidator.getValidationError(password);
_loginStates.value = _states.copyWith(
passwordError: passwordError,
password: password,
);
_checkUpdateButtonState();
}
void _checkUpdateButtonState() {
final hasAllFieldsFilled = _states.email.isNotEmpty && _states.password.isNotEmpty;
final hasNoErrors = _states.emailError == ValidationError.none &&
_states.passwordError == ValidationError.none;
_loginStates.value = _states.copyWith(
showButton: hasNoErrors && hasAllFieldsFilled,
);
}
void onTapLoginButton() {
_loginStates.value = _states.copyWith(
showButton: false,
);
}
void onDispose() {
_loginStates.dispose();
}
}
Efficient Builder
Widget _buildEmailField(BuildContext context) {
return EfficientBuilder(
buildWhen: (p, n) {
return p.emailError != n.emailError;
},
valueListenable: _viewModel.loginStates,
builder: (context, state, _) {
return CustomTextField(
controller: _emailController,
textFieldName: 'Email',
textFieldType: TextFieldType.email,
errorText: state.emailError?.getError(),
onChanged: (email) => _viewModel.onChangedEmail(email: email),
);
},
);
}
Build Method Extension
Widget _buildPasswordField(BuildContext context) {
return _viewModel.loginStates.build(
buildWhen: (p, n) {
return p.passwordError != n.passwordError;
},
builder: (context, state) {
return CustomTextField(
controller: _passwordController,
textFieldName: 'Password',
errorText: state.passwordError?.getError(),
textFieldType: TextFieldType.password,
onChanged: (password) {
_viewModel.onChangedPassword(password: password);
},
);
},
);
}
BuildFor Extension
Widget _buildLogInButton(BuildContext context) {
return _viewModel.loginStates.buildFor(
select: (state) => state.showButton,
builder: (context, state) {
return PrimaryButton(
label: "LOG IN",
onPressed: () {
onTapLogIn();
_viewModel.onTapLoginButton();
},
minWidth: double.infinity,
isDisabled: !state.showButton,
);
},
);
}