Pass static method and model class via contructor paramters - flutter

I have extracted the DropdownSearch<String> widget and created a independent custom widget extending the features of DropdownSearch<String>. I have done this to reduce code size and encapsulate certain functionality.
On an average a single form contains 5-6 DropdownSearch widgets. Their are multiple such forms.
I am populating the items on the widget via API call using Dio package. I have created model and serializer using built_value packages.
My question is how do I pass the fromJson static method and the model class via const constructor parameters
Code :
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:reference_wrapper/reference_wrapper.dart';
class DropDownSearch extends StatefulWidget {
final Ref<String> itemSelected;
final String url;
final String label;
const DropDownSearch({
required this.itemSelected,
required this.url,
required this.label,
Key? key,
}) : super(key: key);
#override
State<DropDownSearch> createState() => _DropDownSearchState();
}
class _DropDownSearchState extends State<DropDownSearch> {
#override
Widget build(BuildContext context) {
return DropdownSearch<String>(
mode: Mode.MENU,
showSelectedItems: true,
showSearchBox: true,
showAsSuffixIcons: true,
dropdownSearchDecoration: InputDecoration(
label: Text(widget.label),
focusColor: Colors.blue,
border: const OutlineInputBorder(
borderSide: BorderSide(
style: BorderStyle.solid,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select ${widget.label}';
}
return null;
},
onFind: (text) async {
var response = await Dio().get(
widget.url,
);
if (response.statusCode != 200) {}
final ipcStatusList = IpcStatusList.fromJson(response.data); //How to pass this static method as parameter
return ipcStatusList.ipcStatus.toList(); //How to pass this as parameter
},
onChanged: (value) => setState(
() {
widget.itemSelected.ref = value ?? '';
},
),
);
}
}

I think you can make a final variable in your "DropDownSearch" which is the same type of toJson. And when using this Class, pass your static variable.

Related

Flutter/Dart - Update state from an external class

I'm totally new to Flutter/Dart, I've done all the layouts for my application, and now it's time to make my application's API calls. I'm trying to manage the forms as cleanly as possible.
I created a class that manages TextFields data (values and errors), if my API returns an error I would like the screen to update without having to call setState(() {}), is this possible?
In addition, many of my application's screens use values that the user enters in real time, if that happened I would have to call the setState(() {}) methodmany times.
Any idea how to do this with the excess calls to the setState(() {}) method?
I created a test project for demo, these are my files:
File path: /main.dart
import 'package:flutter/material.dart';
import 'login_form_data.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final LoginFormData _loginFormData = LoginFormData();
void _submitLoginForm() {
// Validate and then make a call to the login api
// If the api returns any erros inject then in the LoginFormData class
_loginFormData.setError('email', 'Invalid e-mail');
setState(() {}); // Don't want to call setState
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test App'),
),
body: Padding(
padding: const EdgeInsets.all(30),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
decoration: InputDecoration(
errorText: _loginFormData.firstError('email'),
labelText: 'E-mail',
),
onChanged: (value) => _loginFormData.setValue('email', value),
),
TextField(
decoration: InputDecoration(
errorText: _loginFormData.firstError('password'),
labelText: 'Password',
),
obscureText: true,
onChanged: (value) =>
_loginFormData.setValue('password', value),
),
ElevatedButton(
onPressed: _submitLoginForm,
child: const Text('Login'),
)
],
),
),
),
);
}
}
File path: /login_form_data.dart
import 'form/form_data.dart';
import 'form/form_field.dart';
class LoginFormData extends FormData {
#override
Map<String, FormField> fields = {
'email': FormField(),
'password': FormField(),
'simple_account': FormField(
value: true,
),
};
LoginFormData();
}
File path: /form/form_data.dart
class FormData {
final Map<String, dynamic> fields = {};
dynamic getValue(
String key, {
String? defaultValue,
}) {
return fields[key]?.value ?? defaultValue;
}
void setValue(
String key,
String value,
) {
fields[key].value = value;
}
void setError(
String key,
String error,
) {
fields[key]?.errors.add(error);
}
dynamic firstError(
String key,
) {
return fields[key]?.errors.length > 0 ? fields[key]?.errors[0] : null;
}
FormData();
}
File path: /form/form_field.dart
class FormField {
dynamic value;
List errors = [];
FormField({
this.value,
});
}
You are essentially looking for a State Management solution.
There are multiple solutions (you can read about them here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/options)
State Management allows you to declare when you want your widgets to change state instead of having to imperatively call a setState method.
Flutter recommends Provider as a beginner solution, and you can find many tutorials online.
With that being said, let me show you how to achieve this result with a very basic solution: Change Notifier
Quoting flutter documentation :
” A class that can be extended or mixed in that provides a change
notification API using VoidCallback for notifications.”
We are going to make FormData a Change notifier, and them we are going to make your app listen to changes on the instance, and rebuild itself based on them.
Step 1:
Based on the code you posted, I can tell that you will interact with LoginFormData based on the methods setValue and setError from the parent class FormData. So we are going to make FormData inherit ChangeNotifer, and make a call to notifyListeners() on these two methods.
class FormData extends ChangeNotifier {
final Map<String, dynamic> fields = {};
dynamic getValue(
String key, {
String? defaultValue,
}) {
return fields[key]?.value ?? defaultValue;
}
void setValue(
String key,
String value,
) {
fields[key].value = value;
notifyListeners();
}
void setError(
String key,
String error,
) {
fields[key]?.errors.add(error);
notifyListeners();
}
dynamic firstError(
String key,
) {
return fields[key]?.errors.length > 0 ? fields[key]?.errors[0] : null;
}
FormData();
}
Now, every time you call either setValue or setError, the instance of FormData will notify the listeners.
Step2:
Now we have to setup a widget in your app to listen to these changes. Since your app is still small, it’s easy to find a place to put this listener. But as your app grows, you will see that it gets harder to do this, and that’s where packages like Provider come in handy.
We are going to wrap your Padding widget that is the first on the body of your scaffold, with a AnimatedBuilder. Despite of the misleading name, animated builder is not limited to animations. It is a widget that receives any listenable object as a parameter, and rebuilds itself every time it gets notified, passing down the updated version of the listenable.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final LoginFormData _loginFormData = LoginFormData();
void _submitLoginForm() {
// Validate and then make a call to the login api
// If the api returns any erros inject then in the LoginFormData class
_loginFormData.setError('email', 'Invalid e-mail');
//setState(() {}); No longer necessary
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test App'),
),
body: AnimatedBuilder(
animation: _loginFormData,
builder: (context, child) {
return Padding(
padding: const EdgeInsets.all(30),
child: Center(
child: Column(
//... The rest of your widgets
),
),
);
}
),
);
}
}

Assign object to any widget in Flutter

Good afternoon.
Do you know if there is in Flutter something similar to the .NET Tag property or the Qt setProperty property? Basically I want to be able to assign an object, string... to any widget in Flutter.
Thank you very much.
A basic example, as a followup to the comments:
enum DataType {
text,
number,
date,
time,
dateTime,
boolean,
}
class FieldData<T> {
final String table;
final DataType type;
final T value;
FieldData({
required this.table,
required this.type,
required this.value,
});
}
class SpecialTextField extends StatelessWidget {
const SpecialTextField({
Key? key,
required this.table,
required this.type,
required this.onChanged,
this.controller,
this.decoration,
}) : super(key: key);
final String table;
final DataType type;
final Function(FieldData) onChanged;
// Here you declare TextField's properties you need
// to use in your widget, and then pass them to TextField
final TextEditingController? controller;
final InputDecoration? decoration;
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
decoration: decoration,
onChanged: (value) => onChanged(
FieldData(
table: table,
type: type,
value: value,
),
),
);
}
}
And to use it, you can do:
SpecialTextField(
table: 'users',
type: DataType.text,
onChanged: (data) {
print('Table: ${data.table}');
print('Type: ${data.type.name}');
print('Value: ${data.value}');
},
)
I have found a solution that I like better. It is the Extension methods, a way to add functionalities to a library knowing nothing about its internal implementation. An example in my case:
extension myWidget on Widget {
static Object _data = Object();
Object get tag => _data;
set tag(Object tag) {
_data = tag;
}
}
Widget setTag(Widget wdg, Object tag) {
wdg.tag = tag;
return wdg;
}
Usage:
Container(
width: 500,
child: setTag(
TextField(
obscureText: false,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Nº Historia Clínica',
),
),
"###AUTOCAMPO###|HJ_07_RADIODIAGNOSTICO|HOJA_REFERENCIA|ENTEROTEXTO"
)

Flutter: Good practices for FormFields. Do I create a widget for each type of field?

I want to reuse different types of fields in different forms and I have created a separate Widget that returns TextFormField.
Logically, different types of fields have their own validations and other properties, so I have started looking into inheritance and so on to avoid rewriting same chunks of code.
From what I have learnt, Flutter does not encourage inheritance of widgets, so my question is on the best practices of reusing code for various form fields in flutter to remain readability and keep the code clean.
Any tips?
In my experience, I rarely had the need to use other widgets than the original form fields provided by flutter. What I found useful to reuse are validation functions for each fields, since they often have common needs in term of validation.
These are just two basic samples. I pass them to the validator argument of the form field whenever it's needed.
String? validatorForMissingFields(String? input) {
if (input == null || input.isEmpty || input.trim().isEmpty) {
return "Mandatory field";
}
return null;
}
String? validatorForMissingFieldsAndLength(String? input, int length) {
if (input == null || input.isEmpty || input.trim().isEmpty) {
return "Mandatory field";
}
if (input.length != length) {
return 'Not long enough';
}
return null;
}
In any case, instead of extending a basic widget, I prefer to create a new one containing the basic widget with some fixed properties, and others that can be customized. This example does not involve form fields, but I think it can better explain my point.
///if text is not null, icon is ignored
class RectButton extends StatelessWidget {
final Function()? onPressed;
final String? text;
final IconData? icon;
final Color color;
const RectButton({this.text, this.icon, required this.onPressed, Key? key, this.color = mainLightColor}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: OutlinedButton(
style: ButtonStyle(
side: MaterialStateProperty.all(BorderSide(color: color)),
overlayColor: MaterialStateColor.resolveWith((states) => color.withOpacity(0.5)),
backgroundColor: MaterialStateColor.resolveWith((states) => color.withOpacity(0.3)),
),
onPressed: onPressed,
child: text != null
? Text(
text!,
style: TextStyle(fontWeight: FontWeight.bold, color: color),
)
: Icon(
icon,
color: color,
)),
);
}
}
In order to maintain the same look&feel in all the app, I created a custom button with some 'invisible' widgets above it that allowed me to set some properties without extending a basic widget. The properties I needed to be customized are passed to the constructor.
You can create a class to store only the important things like a label or a controller and then use a wrap widget and a for loop to generate the widgets.
Here's an example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<TextFieldData> _allFieldData = [
TextFieldData(
label: 'field 1',
validator: numberOnlyValidator,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
TextFieldData(
label: 'field 2',
validator: canBeEmptyValidator,
),
TextFieldData(
label: 'field 3',
validator: numberOnlyValidator,
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Form(
child: Wrap(
runSpacing: 16,
spacing: 16,
children: [
for (var fieldData in _allFieldData)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 250),
child: TextFormField(
decoration: InputDecoration(label: Text(fieldData.label)),
controller: fieldData.controller,
autovalidateMode: fieldData.autovalidateMode,
validator: fieldData.validator,
),
)
],
),
),
),
),
);
}
}
const String numbersOnlyError = 'Only numbers';
const String requiredFieldError = 'Required field';
RegExp numbersOnlyRegexp = RegExp(r'^[0-9]\d*(,\d+)?$');
String? numberOnlyValidator(String? value) {
if (value == null || value.isEmpty) {
return requiredFieldError;
} else if (!numbersOnlyRegexp.hasMatch(value)) {
return numbersOnlyError;
}
return null;
}
String? canBeEmptyValidator(String? value) {
return null;
}
class TextFieldData {
final String label;
final String? Function(String?)? validator;
final AutovalidateMode autovalidateMode;
TextEditingController controller = TextEditingController();
TextFieldData({
required this.label,
required this.validator,
this.autovalidateMode = AutovalidateMode.disabled,
});
}
And then you can do whatever you want using the .controller of each item inside _allFieldData
Note: I put everything in the same file for simplicity but you would normally have the class and the validators in separate files.

Why I'm getting the error, Invalid constant value in flutter statfulwidget flutter

In the below code widget.hintText is giving the error, I am trying to make the datepicker as the seperate component and dynamically pass the hinttext value whenever calling it from the other file.
import 'package:date_field/date_field.dart';
import 'package:flutter/material.dart';
class DatePicker extends StatefulWidget {
final String hintText;
DatePicker({
this.hintText,
Key key,
}): super(key: key);
#override
_DatePickerState createState() => _DatePickerState();
}
class _DatePickerState extends State<DatePicker> {
#override
Widget build(BuildContext context) {
return DateTimeFormField(
decoration: const InputDecoration(
hintText: widget.hintText,
hintStyle: TextStyle(color: Colors.black54,fontSize: 16),
errorStyle: TextStyle(color: Colors.redAccent),
suffixIcon: Icon(Icons.event_note),
),
mode: DateTimeFieldPickerMode.date,
autovalidateMode: AutovalidateMode.always,
// validator: (e) => (e?.day ?? 0) == 1 ? 'Please not the first day' : null,
onDateSelected: (DateTime value) {
},
);
}
}
The error comes from the fact of using a variable widget.hint inside of const object InputDecoration
I can't find anywhere in the date_field code where it forces you to use a constant decoration
So you might just remove the const keyword in front of InputDecoration
See this answer for details about the difference between const and final
Try removing the const for the InputDecoration()
You can try removing the final keyword from the string

Flutter Bloc conflicting states

I'm trying to build a login activity using Bloc, with the help of the tutorials available at https://bloclibrary.dev/. I've successfully combined the Form Validation and Login Flow into a working solution, but things took a messy turn when adding a button to toggle password visibility.
Figured I'd follow the same format that the validations and login state had (widget's onPressed triggers an event, bloc processes it and changes state to update view), but because states are mutually exclusive, toggling the password visibility causes other information (like validation errors, or the loading indicator) to disappear, because the state that they require in order to be shown is no longer the active one.
I assume one way to avoid this is to have a separate Bloc to handle just the password toggle, but I think that involves nesting a second BlocBuilder in my view, not to mention implementing another set of Bloc+Events+States, which sounds like it might make the code harder to understand/navigate as things get more complex. Is this how Bloc is meant to be used, or is there a cleaner approach that works better here to avoid this?
class LoginForm extends StatefulWidget {
#override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
_onLoginButtonPressed() {
BlocProvider.of<LoginBloc>(context).add(
LoginButtonPressed(
username: _usernameController.text,
password: _passwordController.text,
),
);
}
_onShowPasswordButtonPressed() {
BlocProvider.of<LoginBloc>(context).add(
LoginShowPasswordButtonPressed(),
);
}
return BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginFailure) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('${state.error}'),
backgroundColor: Colors.red,
),
);
}
},
child: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return Form(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Username', prefixIcon: Icon(Icons.person)),
controller: _usernameController,
autovalidate: true,
validator: (_) {
return state is LoginValidationError ? state.usernameError : null;
},
),
TextFormField(
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
state is! DisplayPassword ? Icons.visibility : Icons.visibility_off,
color: ColorUtils.primaryColor,
),
onPressed: () {
_onShowPasswordButtonPressed();
},
),
),
controller: _passwordController,
obscureText: state is! DisplayPassword ? true : false,
autovalidate: true,
validator: (_) {
return state is LoginValidationError ? state.passwordError : null;
},
),
Container(height: 30),
ButtonTheme(
minWidth: double.infinity,
height: 50,
child: RaisedButton(
color: ColorUtils.primaryColor,
textColor: Colors.white,
onPressed: state is! LoginLoading ? _onLoginButtonPressed : null,
child: Text('LOGIN'),
),
),
Container(
child: state is LoginLoading
? CircularProgressIndicator()
: null,
),
],
),
),
);
},
),
);
}
}
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final UserRepository userRepository;
final AuthenticationBloc authenticationBloc;
bool isShowingPassword = false;
LoginBloc({
#required this.userRepository,
#required this.authenticationBloc,
}) : assert(userRepository != null),
assert(authenticationBloc != null);
LoginState get initialState => LoginInitial();
#override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
if (event is LoginShowPasswordButtonPressed) {
isShowingPassword = !isShowingPassword;
yield isShowingPassword ? DisplayPassword() : LoginInitial();
}
if (event is LoginButtonPressed) {
if (!_isUsernameValid(event.username) || !_isPasswordValid(event.password)) {
yield LoginValidationError(
usernameError: _isUsernameValid(event.username) ? null : "(test) validation failed",
passwordError: _isPasswordValid(event.password) ? null : "(test) validation failed",
); //TODO update this so fields are validated for multiple conditions (field is required, minimum char size, etc) and the appropriate one is shown to user
}
else {
yield LoginLoading();
final response = await userRepository.authenticate(
username: event.username,
password: event.password,
);
if (response.ok != null) {
authenticationBloc.add(LoggedIn(user: response.ok));
}
else {
yield LoginFailure(error: response.error.message);
}
}
}
}
bool _isUsernameValid(String username) {
return username.length >= 4;
}
bool _isPasswordValid(String password) {
return password.length >= 4;
}
}
abstract class LoginEvent extends Equatable {
const LoginEvent();
#override
List<Object> get props => [];
}
class LoginButtonPressed extends LoginEvent {
final String username;
final String password;
const LoginButtonPressed({
#required this.username,
#required this.password,
});
#override
List<Object> get props => [username, password];
#override
String toString() =>
'LoginButtonPressed { username: $username, password: $password }';
}
class LoginShowPasswordButtonPressed extends LoginEvent {}
abstract class LoginState extends Equatable {
const LoginState();
#override
List<Object> get props => [];
}
class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginValidationError extends LoginState {
final String usernameError;
final String passwordError;
const LoginValidationError({#required this.usernameError, #required this.passwordError});
#override
List<Object> get props => [usernameError, passwordError];
}
class DisplayPassword extends LoginState {}
class LoginFailure extends LoginState {
final String error;
const LoginFailure({#required this.error});
#override
List<Object> get props => [error];
#override
String toString() => 'LoginFailure { error: $error }';
}
Yup, you are not supposed to have this. // class DisplayPassword extends LoginState {}
And yes, if you want to go pure BLoC it's the correct way to go imo. In this case, because the only state you want to hold is a single bool value, you can go with a simpler approach with the BLoC structure. What I mean is, you don't need to make complete set, event class, state class, bloc class but instead just the bloc class. And on top of that, you can separate the bloc folder into 2 kinds.
bloc
- full
- login_bloc.dart
- login_event.dart
- login_state.dart
- single
- password_visibility_bloc.dart
class PasswordVisibilityBloc extends Bloc<bool, bool> {
#override
bool get initialState => false;
#override
Stream<bool> mapEventToState(
bool event,
) async* {
yield !event;
}
}