I have a screen that I'd put all body in an Obx . I have a Row like bellow
Row(
mainAxisSize: MainAxisSize.min,
children: [
Visibility(
visible: !lastController.posts[i].isDownloaded,
child: GestureDetector(
onTap: () async {
lastController.isDownloading.value = true;
await lastController.setPostIsDownloading(i);
await lastController.Download(i);
await lastController.setPostIsDownloading(i);
lastController.isDownloading.value = false;
},
child: lastController.isDownloading.value?
lastController.posts[i].isDownloading?
SizedBox(
height: 22,
width: 22,
child: CircularProgressIndicator(
backgroundColor: Colors.white,
value:lastController.percentage.value,
),
)
: const Icon(
FontAwesome.download,
color: Colors.white,
)
: const Icon(
FontAwesome.download,
color: Colors.white,
),
),
),
const SizedBox(
width: 10.0,
),
Visibility(
.....
),
],
)
and my Controller is like bellow
class LastMonasebatController extends GetxController {
RxList<Posts> posts = <Posts>[].obs;
Future<void> setPostIsDownloading(int id) async {
posts[id].isDownloading = !posts[id].isDownloading;
posts.refresh();
}
Future<void> Download(int id) async {
........
posts[id].isDownloaded = true;
posts.refresh();
}
}
When I tap on GestureDetector the post downloading until end but the icon does not change and after downloading complete when I hot reload the controller icon disappeared because post.isDownloaded is false . This is not change UI Automatically . What is the problem ?
I want Getx to change the UI automatically
RxList needs a manual update when its elements changes, so it updates on Obx, you need to call refresh() on it like this:
Future<void> Download(int id) async {
........
posts[id].isDownloaded = true;
shabs.refresh();
posts.refresh(); // add this
}
this will tell Obx that the RxList is the same object, but its inside elements did change to update.
and this is an assume, but in your code, there are two checks over isDownloading,
// ...
child: lastController.isDownloading.value?
lastController.posts[i].isDownloading?
// ...
maybe you need to check only over the ones that belongs to the posts[i]:
// ...
child: lastController.posts[i].isDownloading?
// ...
Related
So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code
Created a function to pick file on onPressed() of Elevated Button, and pass the files to a new function openfiles() and then in open files function, I call a widget show() and pass the parameters to it. How to return in listView.
The default constructor takes an explicit List of children. This constructor is appropriate for list views with a small number of children because constructing the List requires doing work for every child that could possibly be displayed in the list view instead of just those children that are actually visible.
pickFile class
return Scaffold(
body: Column(
children: [
Container(
child: ElevatedButton(
onPressed: () async {
final result =
await FilePicker.platform.pickFiles(allowMultiple:true);
if (result == null) return;
openFiles(result.files);
},
child: const Text("pick file"),
),
),
Text("data"),
],
),
);
}
void openFiles(List<PlatformFile> files) {
show(files: files);
}
}
ListView in UI
import 'package:file_picker/src/platform_file.dart';
import 'package:flutter/material.dart';
Widget show({
List<PlatformFile>? files,
}) {
return Scaffold(
body: ListView.builder(
itemCount: files!.length,
itemBuilder: (context, index) {
final file = files[index];
return buildFile(file);
}),
);}
Widget buildFile(PlatformFile file) {
final kb = file.size / 1024;
final mb = kb / 1024;
final size =
(mb >= 1) ? '${mb.toStringAsFixed(2)} MB' : '${kb.toStringAsFixed(2)} KB';
return Container(
width: 100,
height: 100,
child: InkWell(
onTap: () => null,
child: Container(
width: 200,
height: 200,
child: ListTile(
leading: (file.extension == 'jpg' || file.extension == 'png')
? Image.file(
File(file.path.toString()),
width: 80,
height: 80,
)
: Container(
width: 80,
height: 80,
),
title: Text('${file.name}'),
subtitle: Text('${file.extension}'),
trailing: Text(
'$size',
style: TextStyle(fontWeight: FontWeight.w700),
),
),
),
),
);
}
Your show() function is returning a Widget so you have to put it somewhere in the widget tree.
So first I would get rid of your openFiles() function, because it doesn't really have a purpose. Then initialize a List in your _pickFileState class:
List<PlatformFile> files = [] //ANOTHER EDIT HERE,
Then you insert you widget returned by show() into widget tree:
Column(
children: [
Container(), //your Container here
show(files: files), //here you insert your List
],
),
And lastly when you change your function in 'onPressed' method:
ElevatedButton(
onPressed: () async {
final result = await FilePicker.platform.pickFiles(allowMultiple: true);
if (result == null) return;
files = result.files; //EDIT: THIS PROBABLY CAUSED YOU AN ERROR
setState((){});
},
)
You use setState() to rebuild your Widget.
You must setState your widget after fetching files. return value of show is being ignored. (method show(files: files); is not attached to flutter's widget tree.)
I'm trying to change my icon after I tap on my List Item. I already tried different things: I tried the onTap method but the icon just does not want to change. I'm very new to flutter and I would love to find some help for my problem :). Here is my code.
I already searched for solutions but I didn't got it working in my project
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'To-Do List',
theme: ThemeData(
primaryColor: Colors.white,
brightness: Brightness.dark,
),
home: Scaffold(
appBar: AppBar(title: Text('To-Do List'),
backgroundColor: Colors.amber,
),
body: BodyLayout(),
),
);
}
}
class BodyLayout extends StatefulWidget {
#override
BodyLayoutState createState() {
return new BodyLayoutState();
}
}
class BodyLayoutState extends State<BodyLayout> {
// The GlobalKey keeps track of the visible state of the list items
// while they are being animated.
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
// backing data
List<String> _data = [];
final _isdone = Set<String>();
// bool selected = false;
List<bool> selected = new List<bool>();
Icon notdone = Icon(Icons.check_box_outline_blank);
Icon done = Icon(Icons.check_box);
TextEditingController todoController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 445,
child: AnimatedList(
// Give the Animated list the global key
key: _listKey,
initialItemCount: _data.length,
// Similar to ListView itemBuilder, but AnimatedList has
// an additional animation parameter.
itemBuilder: (context, index, animation) {
// Breaking the row widget out as a method so that we can
// share it with the _removeSingleItem() method.
return _buildItem(_data[index], animation);
},
),
),
TextField(
controller: todoController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'To-Do'
),
),
RaisedButton(
child: Text('Insert item', style: TextStyle(fontSize: 20)),
onPressed: () {
_insertSingleItem();
},
),
RaisedButton(
child: Text('Remove item', style: TextStyle(fontSize: 20)),
onPressed: () {
_removeSingleItem();
},
)
],
);
}
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: Icon(
isdone ? Icons.check_box: Icons.check_box_outline_blank
),
onTap: (){
setState(() {
});
},
),
),
);
}
void _insertSingleItem() {
int insertIndex = 0;
setState(() {
_data.insert(0, todoController.text);
});
// Add the item to the data list.
// Add the item visually to the AnimatedList.
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItem() {
int removeIndex = 0;
// Remove item from data list but keep copy to give to the animation.
String removedItem = _data.removeAt(removeIndex);
// This builder is just for showing the row while it is still
// animating away. The item is already gone from the data list.
AnimatedListRemovedItemBuilder builder = (context, animation) {
return _buildItem(removedItem, animation);
};
// Remove the item visually from the AnimatedList.
_listKey.currentState.removeItem(removeIndex, builder);
}
}```
You have already mentioned the icons above. You simply need to use them instead of declaring new ones again.
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: isdone ? done: notdone, // use the icon variables you have already defined
onTap: (){
setState(() {
// add the item to _isdone set if it is not added and remove it if it is added when tapped on the list item
if(isdone) {
_isdone.remove(item);
} else {
_isdone.add(item);
}
});
},
),
),
);
}
In this code, I have added the item and removed the item in setSate() in the onTap(), so that whenever you tap the list item, _isdone Set gets updated and the build() is reloaded. Which makes your layout and data update itself every time you tap on the list item.
I have a FLUTTER problem that I couldn't solve.
Scenario:
1. Implement a QR reader application.
2. The app, read the QR code
3. When you read the QR code, you redirect me to a user's detail page
Problem:
I want to edit that person's data, that's why place a TexFormField, valid fields, but when I call
FUTURE function to send the parameters by post, transforming the body in a JSON so that my server detects it, the button DOES NOTHING.
This is My code
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child : Text("Escanea el codigo QR ", style: TextStyle(fontSize: 25.0),)
),
),
floatingActionButton: FloatingActionButton(
onPressed: obtenerValorQR,
child: Icon(Icons.settings_overscan,),
backgroundColor:Color(0xFF56AB2F)
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
---------------------------LOGIC -------------------------
Future obtenerValorQR()
async{
_scantemp= await FlutterBarcodeScanner.scanBarcode("#004297", "salir", true);
setState(() {
value=_scantemp;
});
if (value == null) {
Navigator.pushNamed(context, QrPageRoute);
} else {
Navigator.pushNamed(context, HomePageRoute, arguments: value);
}
}
2. App read QR code
Widget _infoPerfilUsuario(BuildContext context , index ){
return Container(
height: 120.0,
child: Card(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: ListTile(
leading: CircleAvatar(backgroundImage:
NetworkImage(widget.usuarios[index].urlFoto), radius: 30.0,),
title: Text("Nombre: ${widget.usuarios[index].nombres}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Apellidos: ${widget.usuarios[index].apellidos}"),
Text("Zona: ${widget.usuarios[index].territorio}")
],
),
),
)
),
);
}
QR DETAIL
4. I WANT TO OTHER PARAMETERS IN DETAILPAGE FOR EXAMPLE " PESO" BUT TH RAISED BUTTON DONT COMPILE THE CODE
Code where I send the "peso" parameter that I implement, but does not do what I am looking for.
widget _botonesAcciones(BuildContext context , int index ){
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
RaisedButton(child: Text("SAVE "), color: Colors.green,
onPressed: () {
final form = formKey.currentState;
if(form.validate()) {
_sendData( context , index );
Navigator.pushNamed(context, QrPageRoute);
}
}
),
],
);
}
I IMPLEMENT THIS FUNCTION IF THE FIELD IS VALIDATED, I just want the data to be sent, I don't want the response body returned, just send the data to my DataBase
Future <void> _sendData (BuildContext context , int index ) async {
final url = Uri.https( _url,'/searchdata.php');
await http.post(url,
body: json.encode({
"id" : "${widget.usuarios[index].idUsuarioMobile}",
"peso" : peso
}),
);
}
Something is wrong?
I think my mistake is in the sendData () function
Hi the solucion is simple:
void _sendData(BuildContext context , int index ) {
var url = Uri.https( _url,'/updatePuntos.php');
http.post(url,
body: json.encode({
"id" : "${widget.usuarios[index].idUsuarioMobile}",
"peso" : peso
}),
);
Looking for me econtre, the answer to my question, was something as simple as returning a void method and sending the data to the server. You should use,
body: json.encode
it will make your life easier.
Im very new to flutter and dart so this might be a basic question. However, what I would like to know is how to implement a swipe to delete method in a listview to delete data from firestore too.
I tried using the Dissmissible function but i dont understand how to display the list and I cant seem to understand how to remove the selected data as well.
This here is my dart code
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: new AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children:
<Widget>[
Text("INVENTORY",textAlign: TextAlign.center,) ,new IconButton(
icon: Icon(
Icons.home,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(widget: MyHomePage()),
);
})]),
),body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future getPosts() async{
var firestore = Firestore.instance;
QuerySnapshot gn = await
firestore.collection("Inventory").orderBy("Name",descending:
false).getDocuments();
return gn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPosts(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:(_, index){
return EachList(snapshot.data[index].data["Name"].toString(),
snapshot.data[index].data["Quantity"]);
});
}
}),
);
}
}
class EachList extends StatelessWidget{
final String details;
final String name;
EachList(this.name, this.details);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Card(
child:new Container(
padding: EdgeInsets.all(8.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new CircleAvatar(child: new Text(name[0].toUpperCase()),),
new Padding(padding: EdgeInsets.all(10.0)),
new Text(name, style: TextStyle(fontSize: 20.0),),
],
),
new Text(details, style: TextStyle(fontSize: 20.0))
],
),
),
);
}
}
You should use Dismissible widget. I used it for an inbox list retrieved from Firestore. Inside your EachList return something like this
return Dismissible(
direction: DismissDirection.startToEnd,
resizeDuration: Duration(milliseconds: 200),
key: ObjectKey(snapshot.documents.elementAt(index)),
onDismissed: (direction) {
// TODO: implement your delete function and check direction if needed
_deleteMessage(index);
},
background: Container(
padding: EdgeInsets.only(left: 28.0),
alignment: AlignmentDirectional.centerStart,
color: Colors.red,
child: Icon(Icons.delete_forever, color: Colors.white,),
),
// secondaryBackground: ...,
child: ...,
);
});
IMPORTANT: in order to remove the list item you'll need to remove the item from the snapshot list as well, not only from firestore:
_deleteMessage(index){
// TODO: here remove from Firestore, then update your local snapshot list
setState(() {
snapshot.documents.removeAt(index);
});
}
Here the doc: Implement Swipe to Dismiss
And here a video by Flutter team: Widget of the week - Dismissilbe
You can use the flutter_slidable package to achieve the same.
You can also check out my Cricket Team on Github in which I have did the same you want to achieve, using same package.
Example for how to use package are written here.
I'd like to add that when deleting a document from Firestore, no await is needed as the plugin automatically caches the changes and then syncs them up when there is a connection again.
For instance, I used to use this method
Future deleteWatchlistDocument(NotifierModel notifier) async {
final String uid = await _grabUID();
final String notifierID = notifier.documentID;
return await _returnState(users.document(uid).collection(watchlist).document(notifierID).delete());
}
in which I was waiting for the call to go through, however this prevented any other call to go through and only allowed one. Removing this await tag however solved my issue.
Now I can delete documents offline, and the changes will sync up with Firestore when a connection is regained. It's pretty cool to watch in the console.
I'd recommend watching this video about offline use with Firestore