Related
i have the following simple full code
import 'dart:developer';
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List myListWidget = [];
late bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
setState(() {
myListWidget.add(
Container(
width: 50,
height: 50,
color: isColorWhie?Colors.white:Colors.red,
)
);
});
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...myListWidget,
TextButton(
onPressed: (){
setState(() {
isColorWhie = !isColorWhie; // here never update
log('done');
});
},
child: const Text('tab to Change color',style: TextStyle(color: Colors.white),)
)
],
),
),
),
);
}
}
i tap on any point on screen to add Container into myListWidget thn call setState(() {}); to update ui.
everything fine now but when i change the isColorWhie to true it should change the color to white but it never update !
i am totally confused why it does not update ? And how could i handle with this ?
For base color change, I am using a separate button, also switching the list value.
One thing variable does update the UI, you need to handle state inside the item(state-management property) or reinitialize the variable to get update state.
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List<bool> myListWidgetState = [];
bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(
() {
myListWidgetState.add(isColorWhie);
},
);
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...myListWidgetState.map(
(e) {
return Container(
width: 50,
height: 50,
color: e ? Colors.white : Colors.red,
);
},
),
TextButton(
onPressed: () {
myListWidgetState = myListWidgetState.map((e) => !e).toList();
setState(() {});
print(isColorWhie);
},
child: const Text(
'tab to Change color',
style: TextStyle(color: Colors.white),
),
),
TextButton(
onPressed: () {
setState(() {
isColorWhie = !isColorWhie;
});
print(isColorWhie);
},
child: const Text(
'tab to Change base color',
style: TextStyle(color: Colors.white),
),
),
],
),
),
),
);
}
}
Since you create a container as an object in GestureDetector and save it to your list, it will not change. It is now permanently saved (of course as long as you do not delete the element) as an entry in your list.
Your logic works exactly as you programmed it. For example, if you were to recompile the app and press the TextButton and then anywhere on your screen, a white container would also appear.
If you want to dynamically change the color of all containers at once, then you can do the following:
class _TestState extends State<Test> {
int containerCounter = 0;
late bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
containerCounter++;
});
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 50,
child: ListView.builder(
shrinkWrap: true,
itemCount: containerCounter,
itemBuilder: ((context, index) {
return Container(
height: 50,
color: isColorWhie ? Colors.white : Colors.red,
);
}),
),
),
TextButton(
onPressed: () {
setState(() {
isColorWhie = !isColorWhie; // here never update
});
},
child: const Text(
'tab to Change color',
style: TextStyle(color: Colors.white),
))
],
),
),
),
);
}
}
I'm quite inexperienced with flutter and have created this script.
When you tap on the red container you create a Row of buttons,
I would like when I click on a button in the Row -> the text of the blue container becomes the same as the text contained in the tapped button
Anyone know how I can do?
Thank you :)
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
void main() => runApp(mainApp());
class mainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Chat(),
);
}
}
class Chat extends StatefulWidget {
const Chat({Key? key}) : super(key: key);
#override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
String text = 'Henlo i am Gabriele!';
List<Container> OutputList = [];
void tool(String text) async {
List ListText = text.split(' ');
for (var i in ListText) {
OutputList.add(
Container(
child: GestureDetector(
onTap: () {},
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(i),
),
),
),
),
),
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
tool(text);
print(OutputList);
});
},
child: Container(
width: 150.0,
height: 50.0,
color: Colors.red,
child: Center(child: Text('START ->')),
),
),
SizedBox(height: 50.0),
Row(
children: OutputList,
),
SizedBox(height: 50.0),
Container(
color: Colors.blue,
width: 200.0,
height: 50.0,
child: Text(''),
),
],
),
),
);
}
}
Yes you can add a few line of code check here i try to solve.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
void main() => runApp(mainApp());
class mainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Chat(),
);
}
}
class Chat extends StatefulWidget {
const Chat({Key? key}) : super(key: key);
#override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
String text = 'Henlo i am Gabriele!';
//step 1 create variable
String newGeneratedText = "";
List<Container> OutputList = [];
void tool(String text) async {
List ListText = text.split(' ');
for (var i in ListText) {
OutputList.add(
Container(
child: GestureDetector(
onTap: () {
//add logic here to concatinate values
setState(() {
newGeneratedText = newGeneratedText + " " + i;//added " " for one space
});
},
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(i),
),
),
),
),
),
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
tool(text);
print(OutputList);
});
},
child: Container(
width: 150.0,
height: 50.0,
color: Colors.red,
child: Center(child: Text('START ->')),
),
),
SizedBox(height: 50.0),
Wrap( // added for fixing more values and solve overflow exceptions error
children: OutputList,
),
SizedBox(height: 50.0),
Container(
color: Colors.blue,
width: 200.0,
height: 50.0,
child: Text(newGeneratedText), //final print values
),
],
),
),
);
}
}
I have a code that outputs fields for the user to fill in (code below. I have shortened it here for ease of reading.). I would like to add one more field to this form, which can upload various photos from the phone gallery (preferably with the ability to delete a photo if the user made a mistake when choosing). How can I implement this?
class FormForDeviceService extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FormForDeviceService();
}
class _FormForDeviceService extends State {
final _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(padding: const EdgeInsets.all(10.0),
child: Form(key: _formKey, child: Column(children: <Widget>[
new Text('What is problem', style: TextStyle(fontSize: 20.0),),
new TextFormField(decoration: const InputDecoration(
hintText: 'Describe the problem',),
ElevatedButton(
onPressed: (){if(_formKey.currentState!.validate()) {_formKey.currentState?.reset();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form completed successfully', style: TextStyle(color: Colors.black),),
backgroundColor: Colors.yellow,));
}},
child: const Text('Submit', style: TextStyle(color: Colors.black),),
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.yellow)),)
],)));
}
}
Page at the moment
my expectations (or something similar)
An another approach is here-
(I have made a separate widget to handle all these things and you just need to attach it in any scrollable widget)
my code is as follow:
Main Code with that custom widget:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_memory/image_picker_widget.dart';
void main() {
runApp(GetMaterialApp(title: 'Flutter', home: Flutter()));
}
class Flutter extends StatefulWidget {
const Flutter({Key? key}) : super(key: key);
#override
State<Flutter> createState() => _FlutterState();
}
class _FlutterState extends State<Flutter> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
centerTitle: true,
),
body: Center(
child: Column(
children: [
//This is the widget I am talking about
ImagePickerWidget()
],
),
),
);
}
}
And now the code for that custom widget:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class ImagePickerWidget extends StatefulWidget {
const ImagePickerWidget({Key? key}) : super(key: key);
#override
State<ImagePickerWidget> createState() => _ImagePickerWidgetState();
}
class _ImagePickerWidgetState extends State<ImagePickerWidget> {
late List<CustomImage> images;
late double size;
late ImagePicker imagePicker;
late int idGenerator;
#override
void initState() {
images = [];
size = 100;
idGenerator = 0;
imagePicker = ImagePicker();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
pickImage();
},
child: Text('Pick Image')),
Wrap(
children: images.map((image) {
return Stack(children: [
SizedBox(
height: size,
width: size,
child: ClipRRect(
child: Image.memory(
image.imageData,
fit: BoxFit.fill,
))),
Positioned(
right: 4,
top: 4,
child: InkWell(
onTap: () {
//delete image
images.removeWhere(
(element) => element.imageData == image.imageData);
setState(() {});
},
child: Container(
color: Colors.white, child: Icon(Icons.clear))))
]);
}).toList())
],
);
}
Future<void> pickImage() async {
// XFile? image = await imagePicker.pickImage(source: ImageSource.camera);
XFile? image = await imagePicker.pickImage(source: ImageSource.gallery);
if (image != null) {
Uint8List imageData = await image.readAsBytes();
int id = idGenerator++;
images.add(CustomImage(imageData: imageData, id: id));
setState(() {});
}
}
}
class CustomImage {
Uint8List imageData;
int id;
CustomImage({required this.imageData, required this.id});
}
You can customize the widget in order to use the images list of that widget or you can simply pass the callbacks for that.
we store file here your can use path(string) instead file
List<File> myfile = [];
image_picker package used here to pick image
image_picker: ^0.8.4+10
call like this in your code
Container(
height: 200,
padding: EdgeInsets.all(4),
child: PickPhoto())
Pick photo widget
class PickPhoto extends StatefulWidget {
const PickPhoto({Key? key}) : super(key: key);
#override
State<PickPhoto> createState() => _PickPhotoState();
}
class _PickPhotoState extends State<PickPhoto> {
#override
Widget build(BuildContext context) {
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
width: 45,
height: 45,
child: ElevatedButton(
onPressed: () async {
var file =
await picker?.pickImage(source: ImageSource.gallery);
setState(() {
myfile.add(File(file!.path));
});
},
child: Text("Add Photo"))),
),
Expanded(
child: ListView.builder(
// physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: myfile.length,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(4),
height: 175,
width: 125,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
setState(() {
myfile.removeAt(index);
});
},
icon: Icon(Icons.close),
),
),
Expanded(
child: Container(
child: myfile[index] == null
? Text("")
: Image.file(
myfile[index],
fit: BoxFit.fill,
),
),
),
],
),
)),
)
],
),
);
}
}
SampleCode
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
ImagePicker? picker;
void main() {
WidgetsFlutterBinding.ensureInitialized();
picker = ImagePicker();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MySQL Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [FormForDeviceService()],
),
);
}
}
List<File> myfile = [];
List<int> f = [1, 2, 3, 4, 5];
List<bool> fs = [false, false, false, true, true];
class FormForDeviceService extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FormForDeviceService();
}
class _FormForDeviceService extends State {
final _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
new Text(
'What is problem',
style: TextStyle(fontSize: 20.0),
),
new TextFormField(
decoration: const InputDecoration(
hintText: 'Describe the problem',
),
),
Container(
height: 200,
padding: EdgeInsets.all(4),
child: PickPhoto()),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState?.reset();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
'Form completed successfully',
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.yellow,
));
}
},
child: const Text(
'Submit',
style: TextStyle(color: Colors.black),
),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.yellow)),
)
],
)));
}
}
class PickPhoto extends StatefulWidget {
const PickPhoto({Key? key}) : super(key: key);
#override
State<PickPhoto> createState() => _PickPhotoState();
}
class _PickPhotoState extends State<PickPhoto> {
#override
Widget build(BuildContext context) {
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
width: 45,
height: 45,
child: ElevatedButton(
onPressed: () async {
var file =
await picker?.pickImage(source: ImageSource.gallery);
setState(() {
myfile.add(File(file!.path));
});
},
child: Text("Add Photo"))),
),
Expanded(
child: ListView.builder(
// physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: myfile.length,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(4),
height: 175,
width: 125,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
setState(() {
myfile.removeAt(index);
});
},
icon: Icon(Icons.close),
),
),
Expanded(
child: Container(
child: myfile[index] == null
? Text("")
: Image.file(
myfile[index],
fit: BoxFit.fill,
),
),
),
],
),
)),
)
],
),
);
}
}
I am trying to to return form after a SimpleDialogOption is clicked in flutter there is actually no error but I cant see the form.
my code
SimpleDialogOption(
padding: EdgeInsets.symmetric(horizontal: 24,vertical: 14),
onPressed: (){
Navigator.of(context).pop();
_Transaction();
},
child: Text('Transaction'),
),
Form _Transaction(){
return Form(
key: _formKey,
child: Column(
children: <Widget>[
const Text('On What did you spend?',
style: TextStyle(fontSize: 16),)
],
)
);
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool showForm=false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// you code here
// SimpleDialogOption(
// padding: EdgeInsets.symmetric(horizontal: 24,vertical: 14),
// onPressed: (){
// setState(()=>showForm=!showForm);
// Navigator.of(context).pop();
//
// },
// child: Text('Transaction'), ),
//
TextButton(
onPressed:(){
setState(()=>showForm=!showForm);
},
child:Text("show form")
),
if(this.showForm)_Transaction()
],
),
),
);
}
Form _Transaction(){
return Form(
child: Column(
children: <Widget>[
const Text('On What did you spend?',
style: TextStyle(fontSize: 16),)
],
)
);
}
}
I solved it by adding return inside on pressed(){}
my code now looks like this:
onPressed: (){
Navigator.of(context).pop();
_Transaction();
},
I'm trying to build a system to create forms with multiples pages. My approach was to separate it in three different parts.
FormPages: The different form pages (Each one will have its own logic to validate fields).
ProjectFormContainer: The container page that holds the pages inside a Navigator.
MultiPageFormController: A controller to manage the navigation between form pages.
I've managed to make some progress adding to the ProjectFormContainer a ChangeNotifierProvider of MultiPageFormController but I'm not sure how to connect the individual formPages logic with the rest of the elements and what's the best way to make a decent architecture for this model.
I hope you guys could give me some advice. Thanks in advance!
Here is a Solution based on Flow Builder from Felix Angelov, the author of Bloc State Management.
I used the following packages:
Flow Builder to manage the flow of our form
Flutter Hooks to get rid of the Stateful Widgets and have a leaner code base
Freezed to manage the immutability of our User Info
[optional] Flutter Color Picker
As you will see if you check Flow Builder documentation, it manages a deck of Form Widgets, one for each step of the Flow. Once it completes the Flow, it pops the Widgets out and gets back to the Main Page with the User Info.
1. Creation of the Flow
FlowBuilder<UserInfo>(
state: const UserInfo(),
onGeneratePages: (profile, pages) {
return [
const MaterialPage(child: NameForm()),
if (profile.name != null) const MaterialPage(child: AgeForm()),
if (profile.age != null) const MaterialPage(child: ColorForm()),
];
},
),
2. Flow Management
At the end of each step, we either continue the flow:
context
.flow<UserInfo>()
.update((info) => info.copyWith(name: name.value));
Or, we complete it:
context
.flow<UserInfo>()
.complete((info) => info.copyWith(favoriteColor: color.value));
3. Main Page
And, on our Main Page, we navigate to the OnboardingFlow and await for it to complete:
userInfo.value = await Navigator.of(context).push(OnboardingFlow.route());
Full source code for easy copy-paste
import 'package:flow_builder/flow_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
part 'main.freezed.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flow Demo',
home: HomePage(),
),
);
}
// MAIN PAGE
class HomePage extends HookWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final userInfo = useState<UserInfo?>(null);
return Scaffold(
backgroundColor:
userInfo.value == null ? Colors.white : userInfo.value!.favoriteColor,
appBar: AppBar(title: const Text('Flow')),
body: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: userInfo.value == null
? ElevatedButton(
onPressed: () async {
userInfo.value =
await Navigator.of(context).push(OnboardingFlow.route());
},
child: const Text('GET STARTED'),
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Welcome, ${userInfo.value!.name}!',
style: const TextStyle(fontSize: 48.0),
),
const SizedBox(height: 48.0),
Text(
'So, you are ${userInfo.value!.age} years old and this is your favorite color? Great!',
style: const TextStyle(fontSize: 32.0),
),
],
),
),
);
}
}
// FLOW
class OnboardingFlow extends StatelessWidget {
const OnboardingFlow({Key? key}) : super(key: key);
static Route<UserInfo> route() {
return MaterialPageRoute(builder: (_) => const OnboardingFlow());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FlowBuilder<UserInfo>(
state: const UserInfo(),
onGeneratePages: (profile, pages) {
return [
const MaterialPage(child: NameForm()),
if (profile.name != null) const MaterialPage(child: AgeForm()),
if (profile.age != null) const MaterialPage(child: ColorForm()),
];
},
),
);
}
}
// FORMS
class NameForm extends HookWidget {
const NameForm({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final name = useState<String?>(null);
return Scaffold(
appBar: AppBar(title: const Text('Name')),
body: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
autofocus: true,
onChanged: (value) => name.value = value,
decoration: const InputDecoration(
labelText: 'Name',
hintText: 'Enter your name',
),
),
const SizedBox(height: 24.0),
ElevatedButton(
child: const Text('Continue'),
onPressed: () {
if (name.value != null && name.value!.isNotEmpty) {
context
.flow<UserInfo>()
.update((info) => info.copyWith(name: name.value));
}
},
)
],
),
),
);
}
}
class AgeForm extends HookWidget {
const AgeForm({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final age = useState<int?>(null);
return Scaffold(
appBar: AppBar(title: const Text('Age')),
body: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButtonFormField<int>(
items: List.generate(
200,
(index) => DropdownMenuItem(
value: index,
child: Text(index.toString()),
),
),
onChanged: (value) => age.value = value,
decoration: const InputDecoration(
labelText: 'Age',
hintText: 'How old are you?',
),
),
const SizedBox(height: 24.0),
ElevatedButton(
child: const Text('Continue'),
onPressed: () {
if (age.value != null) {
context
.flow<UserInfo>()
.update((info) => info.copyWith(age: age.value));
}
},
)
],
),
),
);
}
}
class ColorForm extends HookWidget {
const ColorForm({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final color = useState<Color>(Colors.amber);
return Scaffold(
appBar: AppBar(title: const Text('Favorite Color')),
body: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ColorPicker(
pickerColor: color.value,
onColorChanged: (value) => color.value = value,
pickerAreaHeightPercent: 0.8,
),
const SizedBox(height: 24.0),
ElevatedButton(
child: const Text('Continue'),
onPressed: () {
context.flow<UserInfo>().complete(
(info) => info.copyWith(favoriteColor: color.value));
},
)
],
),
),
);
}
}
// DOMAIN
#freezed
abstract class UserInfo with _$UserInfo {
const factory UserInfo({
String? name,
int? age,
Color? favoriteColor,
}) = _UserInfo;
}