How to remove space between expanded ExpansionPanels in ExpansionPanelList? - flutter

This is an example code for ExpansionPanelList
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
// stores ExpansionPanel state information
class Item {
Item({
required this.expandedValue,
required this.headerValue,
this.isExpanded = false,
});
String expandedValue;
String headerValue;
bool isExpanded;
}
List<Item> generateItems(int numberOfItems) {
return List<Item>.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<Item> _data = generateItems(8);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: ListTile(
title: Text(item.expandedValue),
subtitle:
const Text('To delete this panel, tap the trash can icon'),
trailing: const Icon(Icons.delete),
onTap: () {
setState(() {
_data.removeWhere((Item currentItem) => item == currentItem);
});
}),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
And it gives the following result:
As you see there is grey space between Panel 0 and Panel 1, and between Panel 1 and Panel 2. Could anyone say how to remove this space, if it is possible?

This space is added by MaterialGap inside source code.
if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
items.add(MaterialGap(
key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
You can remove/comment this part or better create a local project file and comment this part.
To use your customized ExpansionPanelList, import your file like
import 'customized_expansionlist.dart' as customExp;
...
customExp.ExpansionPanelList(... customExp.ExpansionPanel(...))

Related

Flutter TextField does not follow ListTiles within ReorderableListView

I'm trying to use a TextFormField within a ReorderableListView and can't seem to get the text to follow the moving ListTile. Below is a minimally modified copy of the ReorderableListView sample code that reproduces the issue. All that's added is a list of Strings and a TextFormField per ListTile.
The example includes a Text widget that tracks with the movement of the ListTile. The difference I see with Text is that it is possible to update the Text constructor in the build method of the ListTile.
Using an itemBuilder: instead of the static children: property seems to make no difference.
What am I missing here? Thanks in advance!
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<int> _items = List<int>.generate(50, (int index) => index);
final List<String> _textValues = List<String>.generate(50, (int index) => '');
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
padding: const EdgeInsets.symmetric(horizontal: 40),
children: <Widget>[
for (int index = 0; index < _items.length; index += 1)
ListTile(
key: Key('$index'),
tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
title: Text('Item ${_items[index]}: ${_textValues[index]}'),
subtitle: TextFormField(
initialValue: _textValues[index],
onChanged: (String? value) {
setState(() {
_textValues[index] = value!;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final int item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
final String textValue = _textValues.removeAt(oldIndex);
_textValues.insert(newIndex, textValue);
});
},
);
}
}
It appears as though this works if I wrap the int and String in an object, and then use a single ObjectKey as the key for the ListTile. I guess the lesson here is that even if multiple lists of properties are in sync, some widgets cache or something and aren't properly rebuilt, while others are.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class Item {
int? item;
String? text;
Item(this.item, this.text);
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<Item> _listItems =
List<Item>.generate(50, (int index) => Item(index, ''));
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
padding: const EdgeInsets.symmetric(horizontal: 40),
children: <Widget>[
for (int index = 0; index < _listItems.length; index += 1)
ListTile(
key: ObjectKey(_listItems[index]),
tileColor:
_listItems[index].item!.isOdd ? oddItemColor : evenItemColor,
title: Text(
'Item ${_listItems[index].item}: ${_listItems[index].text}'),
subtitle: TextFormField(
initialValue: _listItems[index].text,
onChanged: (String? value) {
setState(() {
_listItems[index].text = value!;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final Item listItem = _listItems.removeAt(oldIndex);
_listItems.insert(newIndex, listItem);
});
},
);
}
}

Flutter - How to change button color on the click and disable other buttons?

I have a listview with several green buttons, and I need to change the color of a button to red on click. The problem is that in doing that all the other buttons need to go back to their base color green.
On this example below (working version at https://www.dartpad.dev/?id=b4ea6414b6a4ffcc7135579e673be845) All buttons change the color on click independently of the other buttons, but the desired effect is that all the other buttons should be green when the clicked button is red.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MyWidget(
text: 'Button 1',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 2',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 3',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 4',
onPressed: () => print('Click'),
),
],
)),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({
Key? key,
required this.text,
required this.onPressed,
}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
final String text;
final VoidCallback onPressed;
}
class _MyWidgetState extends State<MyWidget> {
bool isFavourte = false;
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavourte ? Colors.red : Colors.green,
),
onPressed: () {
setState(() => isFavourte = !isFavourte);
widget.onPressed();
},
child: Text(widget.text));
}
}
How this can be done?
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final selectedIndexNotifier = ValueNotifier<int?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ValueListenableBuilder<int?>(
valueListenable: selectedIndexNotifier,
builder: (_, selectedIndex, __) => Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (int i = 1; i <= 4; i++)
MyWidget(
key: ValueKey(i),
text: 'Button $i',
isFavorite: selectedIndex == i,
onPressed: () => selectedIndex == i ? selectedIndexNotifier.value = null : selectedIndexNotifier.value = i
)
],
))),
),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({
Key? key,
required this.text,
required this.isFavorite,
required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) => ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavorite ? Colors.red : Colors.green,
),
onPressed: onPressed,
child: Text(text));
final String text;
final bool isFavorite;
final VoidCallback onPressed;
}
Here's an example doing exactly what you want to achieve, by saving the state of each button on a List and updating them all as one changes:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Map> buttonList = [
{
'label': 'button1',
'active': true,
},
{
'label': 'button2',
'active': true,
},
{
'label': 'button3',
'active': true,
},
{
'label': 'button4',
'active': true,
},
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: buttonList.length,
itemBuilder: (context, index){
return ElevatedButton(
onPressed: () => onPressed(buttonList[index]),
style: ButtonStyle(
backgroundColor: buttonList[index]['active']
? MaterialStateProperty.all(Colors.green)
: MaterialStateProperty.all(Colors.red),
),
child: Text(buttonList[index]['label']),
);
},
),
);
}
void onPressed(Map button){
setState(() {
for (var element in buttonList) {
element['active'] = false;
}
button['active'] = true;
});
}
}
created selectedValue variable in myWidget2 and id for evrey button so when ever you press a button it going to set selectedValue = id so that only the button whit the id = selectedValue going to turn red
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
int selectedValue = 0 ;
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget2() ,
);
}
}
class ValueChanged extends Notification {
final int selectedValue ;
ValueChanged(this.selectedValue);
}
class MyWidget2 extends StatefulWidget {
const MyWidget2({
Key? key,
}) : super(key: key);
#override
State<MyWidget2> createState() => _MyWidget2State();
}
class _MyWidget2State extends State<MyWidget2> {
int selectedValue = 0 ;
#override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener<ValueChanged>(
onNotification: (n) {
setState(() {
selectedValue = n.selectedValue ;
// Trigger action on parent via setState or do whatever you like.
});
return true;
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MyWidget(
text: 'Button 1',
onPressed: () => print('Click'),
id: 1,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 2',
onPressed: () => print('Click'),
id: 2,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 3',
onPressed: () => print('Click'),
id:3,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 4',
onPressed: () => print('Click'),
id:4,
selectedValue :selectedValue ,
),
],
)),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({
Key? key,
required this.text,
required this.onPressed,
required this.id,
required this.selectedValue,
}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
final int id;
final String text;
final VoidCallback onPressed;
final int selectedValue ;
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: widget.id == widget.selectedValue ? Colors.red : Colors.green,
),
onPressed: () {
setState(() => ValueChanged(widget.id).dispatch(context));
widget.onPressed();
},
child: Text(widget.text));
}
}
First I've made MyWidget Stateless and Create Two new things:
ButtonData Class: Separate The Actual data that needs to be controlled and makes it scalable.
MyButtonList: StatefulWidget that contains a List of boolean values to track the current active Button
here's an example:
Create new file and copy the following code and see the result:
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.blue[800],
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyButtonList(
buttons: [
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
],
),
),
),
);
}
}
class MyButtonList extends StatefulWidget {
const MyButtonList({Key? key, required this.buttons}) : super(key: key);
final List<ButtonData> buttons;
#override
State<MyButtonList> createState() => _MyButtonListState();
}
class _MyButtonListState extends State<MyButtonList> {
late List<bool> favoriateState;
#override
void initState() {
favoriateState = List.generate(
widget.buttons.length, (index) => widget.buttons[index].isFavorite);
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (var i = 0; i < widget.buttons.length; i++)
MyWidget(
text: widget.buttons[i].text,
onPressed: () {
for (var j = 0; j < favoriateState.length; j++) {
favoriateState[j] = false;
}
setState(() {
favoriateState[i] = true;
if (widget.buttons[i].onPressed != null) {
widget.buttons[i].onPressed!();
}
});
},
isFavourte: favoriateState[i],
),
],
);
}
}
class ButtonData {
final String text;
final Function()? onPressed;
final bool isFavorite;
ButtonData({required this.text, this.onPressed, this.isFavorite = false});
}
class MyWidget extends StatelessWidget {
const MyWidget(
{Key? key,
required this.text,
required this.onPressed,
this.isFavourte = false})
: super(key: key);
final String text;
final Function()? onPressed;
final bool isFavourte;
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavourte ? Colors.red : Colors.green,
),
onPressed: onPressed,
child: Text(text));
}
}
You seem to want to emulate RadioButtons by using TextButtons.
Withing a group of RadioListTile-s only one can be active. And this is what you want to achieve, if I understood you correctly.
May I suggest to use RadioListTile-s instead and then style (or theme) these as you like: Green for inactive Tiles, Red for active Tiles.
The following just demonstrates the usage of RadioListTile, further info on styling active-/nonactive-Tiles can be found easily.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// main application widget
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Application';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
/// stateful widget that the main application instantiates
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
enum Fruit { apple, banana }
/// private State class that goes with MyStatefulWidget
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Fruit? _fruit = Fruit.apple;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RadioListTile<Fruit>(
title: const Text('Apple'),
value: Fruit.apple,
groupValue: _fruit,
onChanged: (Fruit? value) {
setState(() {
_fruit = value;
});
},
),
RadioListTile<Fruit>(
title: const Text('Banana'),
value: Fruit.banana,
groupValue: _fruit,
onChanged: (Fruit? value) {
setState(() {
_fruit = value;
});
},
),
],
),
);
}
}
Source
https://googleflutter.com/flutter-radiolisttile/

Flutter Could not find the correct Provider<> above Widget when trying to create ExpansionPanelList

I am trying to build ExpansionPanelList and im using Provider there. To be honest i cannot fully understand the Provider but I am using it as i could not figure other way to make it work. I have seen provider used similar way in somewhere else so i think im not completely wrong here?
Thanks for any help!
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Product {
final String name;
final String price;
Product({this.name, this.price});
}
class DatabaseService {
final CollectionReference productsCollection =
Firestore.instance.collection('products');
List<Product> productListFromSnapshot(QuerySnapshot snapshot) {
// cycle through each document and create product list
return snapshot.documents.map((doc) {
return Product(
name: doc.data['name'] ?? "",
price: doc.data['price'] ?? 0,
);
}).toList();
}
Stream<List<Product>> get products {
return productsCollection.snapshots().map(productListFromSnapshot);
}
}
class ProductList extends StatefulWidget {
ProductList({Key key}) : super(key: key);
#override
_ProductListState createState() => _ProductListState();
}
class _ProductListState extends State<ProductList> {
#override
Widget build(BuildContext context) {
final products = Provider.of<List<Product>>(context) ?? [];
return Container(
child: ExpansionPanelList.radio(
children: products.map<ExpansionPanel>((Product product) {
return ExpansionPanelRadio(
value: product.name,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(product.name),
);
},
body: ListTile(
title: Text(product.name),
subtitle: Text('some text here as subtitle text'),
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
products.removeWhere((currentItem) => product == currentItem);
});
},
),
);
}).toList(),
),
);
}
}
____________________
class Home extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue[50],
appBar: AppBar(
title: Text('test'),
backgroundColor: Colors.blue[400],
elevation: 0.0,
actions: <Widget>[
FlatButton.icon(
onPressed: () async {
await _auth.signout();
},
icon: Icon(Icons.person),
label: Text('LogOut'))
],
),
body: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
HomeScreen({Key key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(children: <Widget>[
RaisedButton(
color: Colors.blue[600],
child: Text('ProductList'),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (contex) => ProductList()));
}),
]),
);
}
}

How to add subitems to ExpansionPanelList - ListView.Builder?

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyStatefulWidget(),
),
);
}
}
class Item {
Item({
this.expandedValue,
this.headerValue,
this.isExpanded = false,
});
String expandedValue;
String headerValue;
bool isExpanded;
List<Item> _subItems;
List<Item> get getSubItems => _subItems;
set subItems(List<Item> subItems) {
_subItems = subItems;
}
}
List<Item> generateItems(int numberOfItems) {
return List.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<Item> _data = generateItems(8);
#override
void initState() {
_data.forEach((element) {
element.subItems = generateItems(3);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: Container(
child: ListView.builder(
itemCount: item._subItems.length,
itemBuilder: (context, position) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
position.toString(),
style: TextStyle(fontSize: 22.0),
),
),
);
},
),
),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
I think, ExpansionTile is what you need
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyStatefulWidget(),
),
);
}
}
class Item {
Item({
this.headerValue,
this.expandedValue,
});
String headerValue;
String expandedValue;
List<Item> subItems;
}
List<Item> generateItems(int numberOfItems) {
return List.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<Item> _data = generateItems(8);
#override
void initState() {
_data.forEach((element) {
element.subItems = generateItems(3);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: _data.length,
separatorBuilder: (context, index){
return const Divider(height: 1.0);
},
itemBuilder: (context, index){
final item = _data[index];
return ExpansionTile(
title: Text(item.headerValue),
subtitle: Text(item.expandedValue),
children: item.subItems.map((subItem){
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
subItem.expandedValue,
style: TextStyle(fontSize: 22.0),
),
),
);
}).toList(),
);
},
);
}
}

Flutter Quick Actions change selected Bottom Navigation Bar item

I'm trying to implement home screen quick actions / app shortcuts in my Flutter app. What I'm trying to achieve is when the user launches my app via a quick action, the app changes the selected tab inside the bottom navigation bar. Any help is appreciated.
main.dart:
runApp(
MaterialApp(
theme: Themes.appLightTheme,
darkTheme: Themes.appDarkTheme,
home: QuickActionsController(
child: HomeFrame(currentIndex: 0),
),
My QuickActionsController class:
import 'package:binfinder/screens/HomeFrame.dart';
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
class QuickActionsController extends StatefulWidget {
final HomeFrame child;
QuickActionsController({Key key, this.child}) : super(key: key);
#override
_QuickActionsControllerState createState() => _QuickActionsControllerState();
}
class _QuickActionsControllerState extends State<QuickActionsController> {
final QuickActions quickActions = QuickActions();
int _currentIndex = 0;
#override
void initState() {
super.initState();
_handleQuickActions();
_setupQuickActions();
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_map',
localizedTitle: 'Map',
),
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_map') {
setState(() {
_currentIndex = 1;
});
} else {
setState(() {
_currentIndex = 0;
});
}
});
}
#override
Widget build(BuildContext context) {
widget.child.currentIndex = _currentIndex;
return widget.child;
}
}
In the demo below, direct click app will enter First Page and In Quick Action choose Main view will enter Second Page
_handleQuickActions need to use
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
and use initial index to control page index
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
full code
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
import 'dart:io';
class QuickActionsManager extends StatefulWidget {
final Widget child;
QuickActionsManager({Key key, this.child}) : super(key: key);
_QuickActionsManagerState createState() => _QuickActionsManagerState();
}
class _QuickActionsManagerState extends State<QuickActionsManager> {
final QuickActions quickActions = QuickActions();
#override
void initState() {
super.initState();
_setupQuickActions();
_handleQuickActions();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_main',
localizedTitle: 'Main view',
icon: Platform.isAndroid ? 'quick_box' : 'QuickBox'),
ShortcutItem(
type: 'action_help',
localizedTitle: 'Help',
icon: Platform.isAndroid ? 'quick_heart' : 'QuickHeart')
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_main') {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
} else if (shortcutType == 'action_help') {
print('Show the help dialog!');
}
});
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QuickActions Demo',
home: QuickActionsManager(child: BottomNavigationBarController(initialIndex: 0,)));
}
}
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Home')));
}
}
class Login extends StatelessWidget {
const Login({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Login')));
}
}
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
class _BottomNavigationBarControllerState
extends State<BottomNavigationBarController> {
final List<Widget> pages = [
FirstPage(
key: PageStorageKey('Page1'),
),
SecondPage(
key: PageStorageKey('Page2'),
),
];
final PageStorageBucket bucket = PageStorageBucket();
int _selectedIndex = 0;
Widget _bottomNavigationBar(int selectedIndex) => BottomNavigationBar(
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('First Page')),
BottomNavigationBarItem(
icon: Icon(Icons.list), title: Text('Second Page')),
],
);
#override
void initState() {
_selectedIndex = widget.initialIndex;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
demo, emulator is a little slow when enter Second Page