Wrapping Gesture detector around a card - flutter

Right now i have a button on the body of the page but cannot implement this button (that routes to a different page) to just encapsulate just the card that is
child: new Text(data["data"][index]["title"]),
it is inside of an itemBuilder so i thought i had to do a GestureDetector. Ive tried to put that child into the GestureDetector method but cannot get it to work unless its the whole body.
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Listviews"),
),
body: new GestureDetector(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => StandardsControlPage()),
);
},
child: Container(
padding: EdgeInsets.all(16.0),
child: new ListView.builder(
itemCount: data == null ? 0 : data["data"].length,
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Text(data["data"][index]["title"]),
);},
),
),
),
);
}}
This example wont work if i have multiple buttons to press with different routes, and was wondering If there is anyway to implement a button with that route to just that child, how would i do it?

return new Scaffold(
appBar: new AppBar(
title: new Text("Listviews"),
),
body: Container(
padding: EdgeInsets.all(16.0),
child: new ListView.builder(
itemCount: data == null ? 0 : data["data"].length,
itemBuilder: (BuildContext context, int index) {
return new GestureDetector(
child: new Card(
child: new Text(data["data"][index]["title"]),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StandardsControlPage()),
);
},
);
},
),
));
I think this will work.

Related

Can I trigger grandparent state changes without an external state management library?

I cannot find a satisfactory way for a grandchild widget to trigger a grandparent state change. My app saves and sources its data all from an on-device database.
Ive tried to proceed this far without using a state management library as I thought this was overkill - the app is not complex.
Ive got a ListView (grandparent), which in turn has children that are my own version of ListTiles. There are two icon buttons on each ListTile, one to edit and one to delete - both of which trigger a different alertdialog (grandchild) popup. When I perform an update or delete on the data, it is written to the db and a Future is returned - and then I need the grandparent ListView state to refresh. StatefulBuilders will only give me a way to refresh state on the grandchild (separately from the child), not a way to trigger 'multi level' state change.
Is it time for a state management solution such as BLOC or Riverpod, or is there any other solution?
ListView Grandparent Widget
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// other children here
Expanded(
flex: 11,
child: FutureBuilder<List<MyCustomObject>>(
future: _getQuotes(), // queries the db
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting
&& !snapshot.hasData) {
return const Center(
child: SizedBox(
height: AppDims.smallSizedBoxLoadingProgress,
width: AppDims.smallSizedBoxLoadingProgress,
child: CircularProgressIndicator()
),
);
} else if (snapshot.hasError) {
log(snapshot.error.toString());
log(snapshot.stackTrace.toString());
return Center(child: Text(snapshot.error.toString()));
} else {
// no point using StatefulBuilder here, as i need
// to potentially trigger _getQuotes() again to rebuild the entire ListView
return ListView.builder(
padding: const EdgeInsets.symmetric(
horizontal: AppDims.textHorizontalPadding,
vertical: AppDims.textVerticalPadding
),
itemCount: snapshot.data!.length,
itemBuilder: (context, int index) {
return MyCustomTile(
// tile data mapping from snapshot for MyCustomObject
);
},
);
}
},
)
)
]
);
}
)
);
}
MyCustomTile Child Widget
#override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDims.tileBorderRadius),
side: const BorderSide(
color: Colors.green,
width: 1.5,
)
),
child: ListTile(
// other omitted ListTile params here
trailing: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return EditDialog();
}
).then((_) => setState(() {})), // will only setState on the dialog!
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => DeleteWarningDialog(
widget.id,
AppStrings.price.toLowerCase(),
true
),
),
),
]
),
),
);
}
DeleteWarningDialog Grandchild Widget
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(_buildFinalWarningString()),
actions: [
TextButton(
child: const Text(AppStrings.cancel),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: const Text(AppStrings.delete),
onPressed: () {
_appDatabase.deleteFoo(widget.objectIdToDelete);
Navigator.pop(context);
},
)
],
);
}
you will have to declare a function in the grandParent which is the listView in your case and pass it to parent and children's. but it will be so complicated and not really efficient, using state management would make it a lot easer and clean

Flutter Visibility widget not working third time

I have wrapped ListView.builder inside Visible widget, and the button for its visible property is in a ListTile widget with variable _currencyVisible.
The widget Visible works 2 times i.e. false/hidden(default), then changes to visible when clicked, and again hides on the second click, but it doesn't work after that. Printing on console _currencyVisible shows correct data.
Here's my code:
menuItems(BuildContext context) {
bool _currencyVisible = false;
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return ListView(
children: [
ListTile(
title: FutureBuilder<dynamic>(
future: getUserCurrencySymbol(),
builder:(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return Text("Currency " + snapshot.data.toString());
}),
trailing: IconButton(
icon: Icon(Icons.refresh),
onPressed: () { setState(() { _currencyVisible = !_currencyVisible; }); },
),
),
Visibility(
visible: _currencyVisible,
child: ListView.builder(
shrinkWrap: true,
itemCount:
currency.allCurrenciesList.length,
itemBuilder: (context, index) {
for (Currency c in currency.allCurrenciesList) {
currency.allCurrenciesList.removeAt(0);
return Card(
child: ListTile(
title: Text(c.country),
subtitle: Text(c.shortCurrency),
trailing: Text(c.symbol),
onTap: () {
saveUserCurrency(c.country, context);
},
),
);
}
return Text("Not Null");
},
),
),
],
);
},
);
}
You are removing all of the data from your currency list. The widget is showing correctly, but there is no data to display.
Remove this line
currency.allCurrenciesList.removeAt(0);
Don't loop through the currencies in itemBuilder. Use index instead.
Visibility(
visible: _currencyVisible,
child: ListView.builder(
shrinkWrap: true,
itemCount: currency.allCurrenciesList.length,
itemBuilder: (context, index) {
final c = currency.allCurrenciesList[index];
return Card(
child: ListTile(
title: Text(.country),
subtitle: Text(c.shortCurrency),
trailing: Text(c.symbol),
onTap: () {
saveUserCurrency(c.country, context);
},
);
}
return Text("Not Null");
),
),

Flutter application Navigation Issue

There are 2 screens in my application. In first screen I am listing all data from my sqflite database. In second screen I am giving functionality to delete that record. But when I pop that screen from the stack. It should be refreshed. How can I achieve that.
This is my first screen return code.
return FutureBuilder<List>(
future: DatabaseHelper.instance.queryAll(),
initialData: List(),
builder: (context, snapshot) {
return snapshot.hasData
? new ListView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
print("value : " + snapshot.data.toString());
return new Card(
child: Column(mainAxisSize: MainAxisSize.min, children: <
Widget>[
ListTile(
leading:
Image.file(File(snapshot.data[i]["thumbnail_url"])),
title: Text(
snapshot.data[i]["title"],
style: _biggerFont,
),
subtitle: Text(snapshot.data[i]["month"] +
", " +
snapshot.data[i]["year"]),
onTap: () {
},
),
ButtonBar(
children: <Widget>[
Visibility(
visible: _isUrduAvail,
child: FlatButton(
child: const Text('اردو'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => OfflinePdfViewer(
snapshot.data[i]["id"].toString(),
snapshot.data[i]["title"],
snapshot.data[i]["urdu_url"],
"Urdu")));
},
),
),
Visibility(
visible: _isEnglishAvail,
child: FlatButton(
child: const Text('English'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => OfflinePdfViewer(
snapshot.data[i]["id"].toString(),
snapshot.data[i]["title"],
snapshot.data[i]["english_url"],
"English")));
},
),
),
Visibility(
visible: _isHindiAvail,
child: FlatButton(
child: const Text('हिन्दी'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => OfflinePdfViewer(
snapshot.data[i]["id"].toString(),
snapshot.data[i]["title"],
snapshot.data[i]["hindi_url"],
"Hindi")));
},
),
),
],
)
]));
},
)
: Center(
child: CircularProgressIndicator(),
);
},
);
In this screen i am building the list and populate the FutureBuilder.
and in second screen I have button on that button click the record will be deleted but what will be the route to call that it can be refreshed?
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) => LibraryScreen(),
),
(route) => false,
);
I have tried this code but it clear all my activity stack.
When you call second screen try to use 'pushReplacement' instead of 'push'
If you pushAndRemoveUntil you cannot go back to the same page. You need to push it again in which case the FutureBuilder will rebuild and you will see the correct data.
Or a better solution would be to get your data as a Stream instead of a Future and use StreamBuilder instead of FutureBuilder.
You can try to use 'Pushreplacement' because it will dispose the previous route.

Flutter: How to add a text line above a ListView inside a Container

I have the following code in one of my screens. I need to add a line of text above the ListView widget. I've tried adding a Column widget above and below the Container that holds the ListView, but I can't seem to get it right.
Can anyone tell me how I do this?
Cheers,
Paul
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: rmoAppBar(subText: 'My Jobs'),
drawer: RmoMenu(),
body: isLoading
? Center(child: CircularProgressIndicator())
: Container(
child: ListView.builder(
itemCount: jobs.sjRows.length,
itemBuilder: (BuildContext context, int index) {
return jobCard(jobs.sjRows, index);
},
),
),
),
onWillPop: () => logoutAlert(context: context),
);
}
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: rmoAppBar(subText: 'My Jobs'),
drawer: RmoMenu(),
body: isLoading
? Center(child: CircularProgressIndicator())
: Column(
children: [
Text('Your text goes here'),
Expanded(
child: ListView.builder(
itemCount: jobs.sjRows.length,
itemBuilder: (BuildContext context, int index) {
return jobCard(jobs.sjRows, index);
},
),
)
],
),
),
onWillPop: () => logoutAlert(context: context),
);
}

Creating a new widget on User Click(Flat Button)

I have a Post class that creates a Post model. And I want to create this model every time a user clicks the flat button. What's the best way to go about this using the onPressed function?
It's going to be a post that holds the text the user adds to the text field and when they submit it will show on a new post.
u can try use listview.builder, this the simple example how to use it. i just edit default code when we created new project.
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView.builder(
itemCount: _counter,
reverse: true,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
color: Colors.red,
child: Center(child: Text('data $index')),
),
);
},
)
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
Basically you want to add items to a list, and then render the contents of that list.
List<String> messages = [];
...
onPressed: ()=> setState(()=>messages.add("SomeText"))
Then render the list:
//Can use the map() api, to convert the list of Strings into a list of widgets:
List<Widget> children = messages.map((m) => Text(m));
return ListView(children: children);
//Or, use ListView.builder() to create the widgets on demand:
return ListView.builder(
itemBuilder: (context, index)=>Text(messages[index]),
itemCount: messages.length
)
The builder method is better optimized for large lists.
here is simple demo
List<String> posts = [];
#override
Widget build(BuildContext context) {
return Scaffold(body: Column(
children: <Widget>[
ListView.builder(
itemCount: posts.length,
itemBuilder: (BuildContext ctxt, int index) {
return new Text(posts[index]);//use any widget
}
),
FloatingActionButton(
backgroundColor: Colors.white,
child: Icon(
Icons.close,
color: Colors.red,
),
onPressed: () {
setState(() {
posts.add(newpost);//add what you want
});
},
),
]),
);
}
i hope it helps..