flutter drawer removes items when closed - flutter

I have a drawer where the user can add items to it which are links to a different page. It works, but if I add an item and then close the drawer, when I open the drawer again that item is gone (other than the default one I have in a list). I have the code below
class SkillDrawer extends StatefulWidget {
#override
State<SkillDrawer> createState() => SkillDrawerState();
}
class SkillDrawerState extends State<SkillDrawer> {
List<Skill> _skills = [
Skill(title: "Test Title 1", pointCap: 500, id: DateTime.now().toString())
];
void _addNewSkill(String sklTitle, double sklPoints) {
final newSkill = Skill(
title: sklTitle,
pointCap: sklPoints,
id: DateTime.now().toString(),
);
setState(() {
_skills.add(newSkill);
});
}
void _startAddNewSkill(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (bCtx) {
return NewSkill(_addNewSkill);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Subjects'),
),
// ## the container listing added items to drawer
Container(
height: double.maxFinite,
child: ListView.builder(
itemBuilder: (ctx, index) {
return ListTile(
title: Text(_skills[index].title),
onTap: () =>
Navigator.of(ctx).pushNamed('/skills', arguments: {
'id': _skills[index].id,
'title': _skills[index].title,
}));
},
itemCount: _skills.length,
))
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: (() => _startAddNewSkill(context)),
),
);
}
}
I want to make it so that the items are not cleared when the drawer is closed, until the app is closed of course.

Related

FilterChip inside ModalBottomSheet

Hi I'm a beginner flutter developer, I have a StatefulWidget widget and a ListView here is a button to display ModalBottomSheet
The ModalBottomSheet has a FilterChip widget that allows the user to apply some filters to the ListView, but I would like to keep the FilterChip state even after the user pop the ModalBottomSheet.
class AvailableMeals extends StatefulWidget {
static const routeName = 'available-meals';
#override
_AvailableMealsState createState() => _DietAvailableMealsState();
}
class _DietAvailableMealsState extends State<DietAvailableMeals> {
bool status = false;
#override
Widget build(BuildContext context) {
buildFilterBox() {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
'SelectFilter',
style: TextStyle(fontSize: 10.sp),
),
),
Container(
child: Wrap(
spacing: 25,
children: [
FilterChip(
selected: status,
label: Text('Vegan'),
onSelected: (value) {
setState(() {
status = value;
});
})
],
),
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: Text('Meals'),
actions: [
IconButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return buildFilterBox();
});
},
icon: Icon(Icons.search))
],
),
body: Container(child : Column(children: [
Expanded(
child: ListView.builder(
itemBuilder: (ctx, index) => ChangeNotifierProvider.value(
value: _customList[index], child: MealCard(_customList[index])),
itemCount: _customList.length,
));
] ))
}

Flutter : i want to pass (title,details,content) to details page display it in vertically in top of the details page?

eg: details about the questions ......................................................when i click to a gridview item i want to pass (title,details,content) to details page display in vertically in top of the details page but when i am pass the data not able to fetch the data in details page i created a constrctor in details page not able to set the data in text and image.
Home Page
----------
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'DetailsPage.dart';
var paddingBottom = 48.0;
class HomePage extends StatelessWidget {
final String apiUrl = "https://www.sofikart.com/MobileApi/banners";
final String apiUrl1 =
"https://wayindia.net/indigo/odia_rashifal/rasifhala.php";
Future<List<dynamic>> fetchUsers() async {
var result = await http.get(Uri.parse(apiUrl1));
return json.decode(result.body)['data'];
}
String id(dynamic user) {
return user['id'];
}
String title(dynamic user) {
return user['title'];
}
String content(dynamic user) {
return user['content'];
}
String eng_title(dynamic user) {
return user['eng_title'];
}
String main_img(dynamic user) {
return user['main_img'];
}
String image_2(dynamic user) {
return user['image_2'];
}
String image_3(dynamic user) {
return user['image_3'];
}
String image_4(dynamic user) {
return user['image_4'];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ଆଜିର ରାଶିଫଳ'),
centerTitle: true,
),
body: Container(
child: FutureBuilder<List<dynamic>>(
future: fetchUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
print(id(snapshot.data[0]));
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 20,
mainAxisSpacing: 25,
),
padding: EdgeInsets.all(13),
shrinkWrap: true,
itemBuilder: (ctx, index) {
return InkWell(
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.all(Radius.circular(12))),
child: Column(
children: [
Expanded(
flex: 9,
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(12)),
child: Image.network(
snapshot.data[index]['main_img'],
fit: BoxFit.fill)),
),
Expanded(
flex: 2,
child: Text(
title(snapshot.data[index]),
style: TextStyle(
color: Colors.black, fontSize: 17),
)),
],
),
),
onTap: () {
print("Click event on Container");
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => DetailsPage()), (route) => false);
},
);
},
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
),
);
}
}
Details Page
------------
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:odia_rasiphala/HomePage.dart';
import 'dart:convert';
class DetailsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
appBar: new AppBar(
title: new Text('ଆଜିର ରାଶିଫଳ'),
leading: new IconButton(
icon: new Icon(Icons.arrow_back_outlined),
onPressed: () => Navigator.pushReplacement(context,
new MaterialPageRoute(builder: (context) => HomePage())),
),
actions: [
IconButton(
onPressed: () {},
icon: Icon(Icons.share),
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Image.network(
'',
width: 200.0,
height: 200.0,
),
new Center(
child: new Text('',style: TextStyle(
color: Colors.black,fontSize: 17
)),
)
],
),
));
}
}
I am guessing you want to pass "eng_title" and "main_img" to details screen.
To do that first make a constructor in your details pages. Example:
class DetailScreen extends StatelessWidget {
// In the constructor, require a Todo.
const DetailScreen({Key? key, required this.eng_title, required this.main_img}) : super(key: key);
// Declare a field that holds the strings passed to this class.
final String eng_title;
final String main_img;
#override
Widget build(BuildContext context) {
// Use the final parameters to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(eng.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(main_img),
),
);
}
}
on your OnTap function, when you click an item on the list, just pass the required parameters like this
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(eng_title: snapshot.data[index]['eng_title'], main_img: snapshot.data[index]['main_img']),
),
);
},
This way you can pass data from onescreen to another. Do not use push and remove until, if you want the user to go back to the list in homepage.
For more info about passing data read the following article by flutter:
https://docs.flutter.dev/cookbook/navigation/passing-data

Add new widget upon clicking floating action button in flutter

I am a beginner in Flutter. I am trying to add a new list item widget to screen when floating action button is pressed. How do I achieve this?
I am trying to create a list of items. When the floating action button is clicked, a dialog box is prompted and user is asked to enter details. I want to add a new list item with these user input details.
This is my input_page.dart file which I am calling in main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MedPage extends StatefulWidget {
#override
_MedPageState createState()=> _MedPageState();
}
class _MedPageState extends State<MedPage> {
Future<String>createAlertDialog(BuildContext context) async{
TextEditingController customController= new TextEditingController();
return await showDialog(context: context,builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: (){
Navigator.of(context).pop(customController.text.toString()); // to go back to screen after submitting
}
)
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
Expanded(
child: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
ReusableListItem(Color(0xFFd2fddf),"Name 1"),
ReusableListItem(Colors.orange,"Name 2"),
ReusableListItem(Color(0xFF57a1ab), "Name 3"),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
print("Clicked");
createAlertDialog(context).then((onValue){
print(onValue);
setState(() {
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour,this.pill);
Color colour;
String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colour,
borderRadius: BorderRadius.circular(15.0)
),
child: Center(
child: Text(pill)
),
);
}
}
You don't need to change much in your code, maintain a variable that stores the values entered to be able to show them in the list. You should use Listview.builder() in order to dynamically render the items.
Here's your code:
class MedPage extends StatefulWidget {
#override
_MedPageState createState() => _MedPageState();
}
class _MedPageState extends State<MedPage> {
List<String> items = [];
Future<String> createAlertDialog(BuildContext context) async {
TextEditingController customController = new TextEditingController();
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop(customController.text
.toString()); // to go back to screen after submitting
})
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return ReusableListItem(Color(0xFFd2fddf), items[index]);
},
itemCount: items.length,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print("Clicked");
createAlertDialog(context).then((onValue) {
// print(onValue);
setState(() {
items.add(onValue);
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour, this.pill);
final Color colour;
final String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration:
BoxDecoration(color: colour, borderRadius: BorderRadius.circular(15.0)),
child: Center(child: Text(pill)),
);
}
}
Firstly you need to use ListView.builder() rather than ListView because you have dynamic content. Also you need to hold your items in a list.
// create a list before
ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Text(list[index]);
}
)
When you click on FloatingActionButton() you will call AlertDialog() method.
FloatingActionButton(
onPressed: (){
AlertDialog(
content: Form(), // create your form here
actions: [
// add a button here
]
)
})
This method will show a dialog(you will add a form inside of the dialog). When the user completes the form(after clicking the button) you will add a new object to the list and update the state with setState({})
onPressed: (){
setState({
// add new object to the list here
});
Navigator.pop(context); // this will close the dialog
}

RefreshIndicator not working with Scrollview

I am trying to use RefreshIndicator with Scrollview as want extra widgits too with list in RefreshIndicator but it is not working.
Basically I want to use RefreshIndicator then inside it a Scrollview with a column with multiple widgets.
Thanks in advance!
Here is my code:
class _DashboardState extends State<Dashboard> {
List<String> _demoData = [];
#override
void initState() {
_demoData = [
"Flutter",
"React Native",
"Cordova/ PhoneGap",
"Native Script"
];
super.initState();
}
#override
Widget build(BuildContext context) {
Future doRefresh() {
return Future.delayed(
Duration(seconds: 0),
() {
setState(() {
print("refresh worked");
_demoData.addAll(["Ionic", "Xamarin"]);
});
},
);
}
return RefreshIndicator(
onRefresh: doRefresh,
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: 400,
child: ListView.builder(
itemBuilder: (ctx, idx) {
return Card(
child: ListTile(
title: Text(_demoData[idx]),
),
);
},
itemCount: _demoData.length,
),
),
],
),
),
);
}
}
The problem is that your list that has the 'RefreshIndicator' is in another list, this only scrolls the parent list and the child list where the 'RefreshIndicator' is located will not work, delete the parent list and have it only display the child list like the following example:
Another problem is also that the 'doRefresh' method is inside the 'build' method of the Widget, take it out of the 'build' method like the example: (in seconds put 2 or 3 seconds to see the animation)
class _DashboardState extends State<Dashboard> {
List<String> _demoData = [];
#override
void initState() {
_demoData = [
"Flutter",
"React Native",
"Cordova/ PhoneGap",
"Native Script"
];
super.initState();
}
Future doRefresh() {
return Future.delayed(
Duration(seconds: 3),
() {
setState(() {
print("refresh worked");
_demoData.addAll(["Ionic", "Xamarin"]);
});
},
);
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: doRefresh,
child: ListView.builder(
itemBuilder: (ctx, idx) {
return Card(
child: ListTile(
title: Text(_demoData[idx]),
),
);
},
itemCount: _demoData.length,
));
}
}
If you want to add more widgets, those widgets have to be inside the ListView, if you want to use more lists it is better to use a CustomScrollView instead of the ListView
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: doRefresh,
child: ListView(
children: [
...List.generate(
_demoData.length,
(idx) => Card(
child: ListTile(
title: Text(_demoData[idx]),
),
)),
Container(
height: 100,
width: double.infinity,
color: Colors.red,
),
...List.generate(
_demoData.length,
(idx) => Card(
child: ListTile(
title: Text(_demoData[idx]),
),
)),
],
));
}

Return variable from current screen to previous screen

So I am implementing a 'settings' view in my Flutter app. The idea is all settings will appear in a ListView, and when the user will click on a ListTile, a showModalBottomSheet will pop where the user will be able to manipulate the setting. The only problem I am having is I am unable to migrate the showModalBottomSheet to a separate class as I cannot make the new function (outside the class) return the manipulated setting variable. This has lead to a messy code, all in a single class.
class Page extends StatefulWidget {
Page({Key key}) : super(key: key);
#override
_Page createState() => _Page();
}
class _Page extends State<Page> {
var value;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text("Age"),
trailing: Text(value),
onTap: () {
setState(() {
value = _valueSelector(); // This doesn't work, but to give an idea what I want
});
},
),
],
);
}
}
int _valueSelector(context) { // Doesn't return
var age = 0;
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Wrap(
children: [
Column(
children: <Widget>[
Slider(
value: age.toDouble(),
min: 0,
max: 18,
divisions: 18,
onChanged: (value) {
setState(() {
age = value.toInt();
});
},
),
],
),
],
);
});
},
).whenComplete(() {
return age; // Not sure if return is supposed to be here
});
}
How can I implement showModalBottomSheet in a separate class and just make it return the variable representing the setting chosen by the user?
You can try the below code,
First, create a class custom_bottom_sheet.dart and add the below code. You can use it everywhere in the project. And also use this library modal_bottom_sheet: ^0.2.0+1 to get the showMaterialModalBottomSheet.
customBottomSheet(BuildContext context, {#required Widget widget}) async {
return await showMaterialModalBottomSheet(
context: context,
backgroundColor: AppColors.transparent_100,
barrierColor: AppColors.black_75,
isDismissible: false,
enableDrag: true,
builder: (_, ScrollController scrollController) {
return widget;
},
);
}
Sample example code:
Create another class called bottom_sheet_example.dart and add the below code.
class BottomSheetExample {
static Future getSheet(BuildContext _context,
{ValueChanged<bool> onChanged}) async {
await customBottomSheet(
_context,
widget: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 40.0, right: 40.0),
height: 170.0,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(27.0),
topRight: Radius.circular(27.0))),
child: Container(
padding: EdgeInsets.only(top: 32),
child: Column(
children: [
Text("Were you at Queen Victoria Building?"),
SizedBox(height: 48),
Row(
children: [
Expanded(
child: RaisedButton(
child: Text("No"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(false);
},
),
),
SizedBox(width: 18),
Expanded(
child: RaisedButton(
child: Text("Yes"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(true);
},
),
),
],
),
SizedBox(height: 24),
],
),
),
)),
);
}
}
Button click to show the bottom sheet
#override
Widget build(BuildContext context) {
return Scaffold(
body: yourBodyWidget(),
bottomNavigationBar: Container(
height: 40,
width: double.infinity,
child: FlatButton(
onPressed: () {
/// call BottomSheetExample class
BottomSheetExample.getSheet(
context,
onChanged: (bool result) async {
///
/// add your code
},
);
},
child: Text("show bottom sheet")),
),
);
}
In onChanged callback you can return your value(obj/String/num/bool/list).
Thank you!