Flutter - Notify child pages - flutter

I have a Parent page, that contains a PageView, like this :
class _ParentState extends State<ParentOverview> {
String title = "" ;
List<String> pageTitles = [
"Page 1",
"Page 2",
] ;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Expenses"),
centerTitle: true,
actions: [
Padding(
padding: EdgeInsets.only(right: 20),
child: InkWell(
child: Icon(Icons.refresh),
onTap: (() {
// TODO
}),
),
),
],
),
body: PageView(
scrollDirection: Axis.vertical,
children: [
ChildPage1(),
ChildPage2(),
],
onPageChanged: ((selectedPage) {
setState(() {
title = pageTitles[selectedPage] ;
});
}),
),
);
}
}
The children of PageView are StatefulWidget.
This Parent page contains an AppBar with a button that is used to reload.
When clicking on this button, I want to make a call that reload the data contained inside Page1 and Page2.
How can I achieve that ?
I have been told to use Provider, but is this the best way to do that ?

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,
));
] ))
}

How to push to a new screen in Flutter without the appbar from the previous screen in Flutter?

I have two screens. In screen1 I have an appbar and a card in the body. IF I click the card it should take me to new screen 'screen2'. I am getting the screen2 but I am also getting the appbar from screen1 as well.. I am doing a push from screen1 to screen2.. May I know how to avoid the appbar from the screen1 in screen2? I have onTap method in VideoCard widget which pushes to screen2... it looks like body of the screen1 is being replaced by screen2.. instead i need to push to screen2 from screen1...
enter image description here
class _AllVideosPageTabletState extends State<AllVideosPageTablet> {
bool searchFlag = false;
String searchText = '';
#override
void initState() {
searchFlag = false;
searchText = '';
super.initState();
}
#override
Widget build(BuildContext context) {
final isLandscape = MediaQuery.of(context).orientation ==
Orientation.landscape;
return Scaffold(
body: Container(
padding: EdgeInsets.all(10.0),
child: Column(
children: [
CupertinoSearchTextField(
onChanged: (value) {
if (value != '') {
setState(
() {
searchFlag = true;
searchText = value;
},
);
}
},
onSubmitted: (value) {
if (value != '') {
setState(
() {
searchFlag = true;
searchText = value;
},
);
}
},
),
Divider(),
Row(
children: [
IconButton(
icon: Image.asset('lib/assets/icons/back.png'),
onPressed: () {
Navigator.pop(context);
},
),
Expanded(
child: Container(
child: Center(
child: Text(
'All Videos',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: '#D86300'.toColor(),
),
),
),
),
),
],
),
searchFlag
? SearchResultsListView(searchText)
: Expanded(
child: FutureBuilder(
future: VideoXML().getDataFromXML(context),
builder: (context, data) {
if (data.hasData) {
List<Video> videoList = data.data;
return GridView.count(
scrollDirection: Axis.vertical,
crossAxisCount: isLandscape ? 4 : 2,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
children: videoList.map(
(video) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: VideoCard(
video.title,
video.name,
video.image,
),
);
},
).toList(),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
),
],
),
),
);
}
Flutter works in a way that every new screen is based on a new Scaffold widget. This way you can customize, whether to show the appBar at all or should be display different appBar on different screens.
For example:
Screen 1 should be a scaffold of its own:
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:'Screen 1'),
body: InkWell(onTap: () => Navigator.of(context).pushedName(routeToSecondScreen),
child: Text('Navigate to secondScreen'),
);
);
}
}
Screen 2 should be a scaffold of its own:
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:'Screen 2'),
body: Text('Screen 2'),
);
}
}
This way you can have different appBars for each screen. If you do not wish to display appBar on the second screen at all, simply do not specify appBar on the second screen as follows.
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text('Screen 2'),
);
}
}

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
}

Flutter Web Dashboard Content - Best Practice?

I am creating an admin dashboard, I currently have two view widgets in a row:
A side bar - 300px (not drawer, because I want it to show permanently) - which has a list.
A content widget - expanded.
Admin Dashboard View
Here is the code for the page:
import 'package:flutter/material.dart';
import 'package:webenrol/widgets/dashboard_admin.dart';
import 'package:webenrol/widgets/drawer.dart';
//TODO: add flappy_search_bar package and add to appBar
class AdminDashboard extends StatelessWidget {
//TODO: Add title
static String id = '/admin_dashboard';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Admin Dashboard - Overview'),),
body: Container(child: Row(
children: <Widget>[
//Sidebar
DashboardSideBar(),
//Main Dashboard Content
DashboardAdmin(),
],
)),
);
}
}
I am going to create other content widgets for the links in the sidebar, what I preferably would like, is have the content widget update to what is clicked on the menu, and also have the ListTile selected as active without the page needing to reload.
Is this possible and is my WebApp laid out correctly for this, or do I need to change it?
So I found a solution, I needed to use a TabController and TabView.
When I setup my TabController, I setup a Listener to listen for any events on its index.
class _State extends State<AdminDashboard> with SingleTickerProviderStateMixin{
int active = 0;
//TODO: Add title
#override
void initState() {
super.initState();
tabController = TabController(length: 5, vsync: this, initialIndex: 0)
..addListener(() {
setState(() {
active = tabController.index;
});
});
}
Then I modified my menu to animate to the correct page onTap and also be selected if the page I was on was true.
Widget adminMenu() {
return ListView(
shrinkWrap: true,
children: <Widget>[
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
selected: tabController.index == 0 ? true : false,
onTap: () {
tabController.animateTo(0);
},
),
ListTile(
leading: Icon(Icons.add),
title: Text('Add New Centre'),
selected: tabController.index == 1 ? true : false,
onTap: () {
tabController.animateTo(1);
},
),
ListTile(
leading: Icon(Icons.list),
title: Text('List Centres'),
selected: tabController.index == 2 ? true : false,
onTap: () {
tabController.animateTo(2);
},
),
ListTile(
leading: Icon(Icons.people),
title: Text('Users'),
selected: tabController.index == 3 ? true : false,
onTap: () {
tabController.animateTo(3);
},
),
ListTile(
leading: Icon(Icons.exit_to_app),
title: Text('Logout'),
selected: tabController.index == 4 ? true : false,
onTap: () {
tabController.animateTo(4);
},
),
],
);
}
Then I had to simply setup my TabBarView in the content area:
return Scaffold(
appBar: AppBar(
//TODO: Make title dynamic to page using tabController.index turnkey operator
title: Text('Admin Dashboard - Overview'),
),
body: Container(
child: Row(
children: <Widget>[
//Responsive Sidebar
DashboardSideBar(),
//Main Dashboard Content
Expanded(
child: TabBarView(controller: tabController,
children: <Widget>[
DashboardAdmin(),
Container(child: Text('Hello World!'),),
Container(child: Text('Page 3'),),
Container(child: Text('Page 4'),),
Container(child: Text('Page 5'),),
],),
),
],
)),
);
I still need to refactor my code and clean it up, but for anyone wanting to create a clean dynamic Web Dashboard this is how :)

Determine Scroll Widget height

I would like to determine if a 'scrollable' widget indeed needs to scroll. I would ultimately like to show something like 'Scroll for more'. How do I achieve this?
I could use a combination of LayoutBuilder and a ScrollController. However, ScrollController gives me maxScrollExtent and minScrollExtent only after any event - like say for example user trying to scroll
I would like to know during 'build' so that I can determine to show 'Scroll for more' or not depending on the screen dimensions
class _MyHomePageState extends State<MyHomePage> {
int value = 0;
String text = 'You have pushed the button 0 times\n';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 9,
child: SingleChildScrollView(
child: Text(text),
),
),
Expanded(
flex: 1,
child: Text('Scroll for more')),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
value += 1;
text = text + 'You have pushed the button $value times \n';
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I would like to dynamically display
Text('Scroll for more'),
only if the SingleChildScrollView needs to be scrolled to view the entire content. Please note, I am just giving the above code as example.
In my real case, I have a StreamBuilder inside a SingleChildScrollView and I cannot determine how much of data is going to be flowing in from the stream. I also do not have any button or tap or any gesture to access the controller and setState
Thanks in advance for any help
class _MyHomePageState extends State<MyHomePage> {
bool showMore = false;
final scrollController = ScrollController();
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(() {
showMore = scrollController.position.maxScrollExtent > 0;
});
});
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 9,
child: SingleChildScrollView(
controller: scrollController,
child: SizedBox(height: 650, child: Text('blah')),
),
),
if (showMore) Expanded(flex: 1, child: Text('Scroll for more')),
],
),
),
);
}
}