Flutter: persistent bottom navigation bar rebuilds ALL pages when navigating between pages - flutter

I am using the persistent_bottom_nav_bar package and have implemented its custom navigation bar (basically just customized from the example in their Readme page). Reproducible code below.
The issue: when you navigate with the bottom navigation, ALL the pages rebuild on every tap. Quite draining on the app's performance! This seems to be a Flutter issue in general and solutions are given by using e.g. an IndexedStack when the full code is written by oneself instead of using a package, which I have done.
Is there any way to fix this issue when using the persistent_bottom_nav_bar package and specifically with the custom code that I have used?
My code (simplified so that anyone can just copy & run it):
main.dart
import 'package:flutter/material.dart';
import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart';
import 'page1.dart';
import 'page2.dart';
import 'page3.dart';
import 'page4.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: HomeScaffold(),
);
}
}
class HomeScaffold extends StatefulWidget {
#override
_HomeScaffoldState createState() => _HomeScaffoldState();
}
class _HomeScaffoldState extends State<HomeScaffold> {
PersistentTabController _controller;
#override
void initState() {
super.initState();
_controller = PersistentTabController(initialIndex: 0);
}
List<Widget> _buildScreens() {
return [
Page1(),
Page2(),
Page3(),
Page4(),
];
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
_buildBottomNavBarItem('Page 1', Icons.home),
_buildBottomNavBarItem('Page 2', Icons.search),
_buildBottomNavBarItem('Page 3', Icons.message),
_buildBottomNavBarItem('Page 4', Icons.settings),
];
}
#override
Widget build(BuildContext context) {
return PersistentTabView.custom(
context,
controller: _controller,
screens: _buildScreens(),
confineInSafeArea: true,
itemCount: 4,
handleAndroidBackButtonPress: true,
stateManagement: true,
screenTransitionAnimation: ScreenTransitionAnimation(
animateTabTransition: true,
curve: Curves.ease,
duration: Duration(milliseconds: 200),
),
customWidget: CustomNavBarWidget(
items: _navBarsItems(),
onItemSelected: (index) {
setState(() {
_controller.index = index;
});
},
selectedIndex: _controller.index,
),
// ),
);
}
}
class CustomNavBarWidget extends StatelessWidget {
final int selectedIndex;
final List<PersistentBottomNavBarItem> items;
final ValueChanged<int> onItemSelected;
CustomNavBarWidget({
Key key,
this.selectedIndex,
#required this.items,
this.onItemSelected,
});
Widget _buildItem(PersistentBottomNavBarItem item, bool isSelected) {
return Container(
alignment: Alignment.center,
height: kBottomNavigationBarHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: IconTheme(
data: IconThemeData(
size: 26.0,
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary == null
? item.activeColorPrimary
: item.inactiveColorPrimary),
child: isSelected ? item.icon : item.inactiveIcon ?? item.icon,
),
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Material(
type: MaterialType.transparency,
child: FittedBox(
child: Text(
item.title,
style: TextStyle(
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary,
fontWeight: FontWeight.w400,
fontSize: 12.0),
)),
),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Container(
width: double.infinity,
height: kBottomNavigationBarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items.map((item) {
int index = items.indexOf(item);
return Flexible(
child: GestureDetector(
onTap: () {
this.onItemSelected(index);
},
child: _buildItem(item, selectedIndex == index),
),
);
}).toList(),
),
),
);
}
}
PersistentBottomNavBarItem _buildBottomNavBarItem(String title, IconData icon) {
return PersistentBottomNavBarItem(
icon: Icon(icon),
title: title,
activeColorPrimary: Colors.indigo,
inactiveColorPrimary: Colors.grey,
);
}

try AutomaticKeepAliveClientMixin, this won't refresh page while change tab:
1.
class PageState extends State<Page> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);

Related

create field for load images

I have a code that outputs fields for the user to fill in (code below. I have shortened it here for ease of reading.). I would like to add one more field to this form, which can upload various photos from the phone gallery (preferably with the ability to delete a photo if the user made a mistake when choosing). How can I implement this?
class FormForDeviceService extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FormForDeviceService();
}
class _FormForDeviceService extends State {
final _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(padding: const EdgeInsets.all(10.0),
child: Form(key: _formKey, child: Column(children: <Widget>[
new Text('What is problem', style: TextStyle(fontSize: 20.0),),
new TextFormField(decoration: const InputDecoration(
hintText: 'Describe the problem',),
ElevatedButton(
onPressed: (){if(_formKey.currentState!.validate()) {_formKey.currentState?.reset();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form completed successfully', style: TextStyle(color: Colors.black),),
backgroundColor: Colors.yellow,));
}},
child: const Text('Submit', style: TextStyle(color: Colors.black),),
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.yellow)),)
],)));
}
}
Page at the moment
my expectations (or something similar)
An another approach is here-
(I have made a separate widget to handle all these things and you just need to attach it in any scrollable widget)
my code is as follow:
Main Code with that custom widget:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_memory/image_picker_widget.dart';
void main() {
runApp(GetMaterialApp(title: 'Flutter', home: Flutter()));
}
class Flutter extends StatefulWidget {
const Flutter({Key? key}) : super(key: key);
#override
State<Flutter> createState() => _FlutterState();
}
class _FlutterState extends State<Flutter> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
centerTitle: true,
),
body: Center(
child: Column(
children: [
//This is the widget I am talking about
ImagePickerWidget()
],
),
),
);
}
}
And now the code for that custom widget:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class ImagePickerWidget extends StatefulWidget {
const ImagePickerWidget({Key? key}) : super(key: key);
#override
State<ImagePickerWidget> createState() => _ImagePickerWidgetState();
}
class _ImagePickerWidgetState extends State<ImagePickerWidget> {
late List<CustomImage> images;
late double size;
late ImagePicker imagePicker;
late int idGenerator;
#override
void initState() {
images = [];
size = 100;
idGenerator = 0;
imagePicker = ImagePicker();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
pickImage();
},
child: Text('Pick Image')),
Wrap(
children: images.map((image) {
return Stack(children: [
SizedBox(
height: size,
width: size,
child: ClipRRect(
child: Image.memory(
image.imageData,
fit: BoxFit.fill,
))),
Positioned(
right: 4,
top: 4,
child: InkWell(
onTap: () {
//delete image
images.removeWhere(
(element) => element.imageData == image.imageData);
setState(() {});
},
child: Container(
color: Colors.white, child: Icon(Icons.clear))))
]);
}).toList())
],
);
}
Future<void> pickImage() async {
// XFile? image = await imagePicker.pickImage(source: ImageSource.camera);
XFile? image = await imagePicker.pickImage(source: ImageSource.gallery);
if (image != null) {
Uint8List imageData = await image.readAsBytes();
int id = idGenerator++;
images.add(CustomImage(imageData: imageData, id: id));
setState(() {});
}
}
}
class CustomImage {
Uint8List imageData;
int id;
CustomImage({required this.imageData, required this.id});
}
You can customize the widget in order to use the images list of that widget or you can simply pass the callbacks for that.
we store file here your can use path(string) instead file
List<File> myfile = [];
image_picker package used here to pick image
image_picker: ^0.8.4+10
call like this in your code
Container(
height: 200,
padding: EdgeInsets.all(4),
child: PickPhoto())
Pick photo widget
class PickPhoto extends StatefulWidget {
const PickPhoto({Key? key}) : super(key: key);
#override
State<PickPhoto> createState() => _PickPhotoState();
}
class _PickPhotoState extends State<PickPhoto> {
#override
Widget build(BuildContext context) {
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
width: 45,
height: 45,
child: ElevatedButton(
onPressed: () async {
var file =
await picker?.pickImage(source: ImageSource.gallery);
setState(() {
myfile.add(File(file!.path));
});
},
child: Text("Add Photo"))),
),
Expanded(
child: ListView.builder(
// physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: myfile.length,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(4),
height: 175,
width: 125,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
setState(() {
myfile.removeAt(index);
});
},
icon: Icon(Icons.close),
),
),
Expanded(
child: Container(
child: myfile[index] == null
? Text("")
: Image.file(
myfile[index],
fit: BoxFit.fill,
),
),
),
],
),
)),
)
],
),
);
}
}
SampleCode
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
ImagePicker? picker;
void main() {
WidgetsFlutterBinding.ensureInitialized();
picker = ImagePicker();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MySQL Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [FormForDeviceService()],
),
);
}
}
List<File> myfile = [];
List<int> f = [1, 2, 3, 4, 5];
List<bool> fs = [false, false, false, true, true];
class FormForDeviceService extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FormForDeviceService();
}
class _FormForDeviceService extends State {
final _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
new Text(
'What is problem',
style: TextStyle(fontSize: 20.0),
),
new TextFormField(
decoration: const InputDecoration(
hintText: 'Describe the problem',
),
),
Container(
height: 200,
padding: EdgeInsets.all(4),
child: PickPhoto()),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState?.reset();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
'Form completed successfully',
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.yellow,
));
}
},
child: const Text(
'Submit',
style: TextStyle(color: Colors.black),
),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.yellow)),
)
],
)));
}
}
class PickPhoto extends StatefulWidget {
const PickPhoto({Key? key}) : super(key: key);
#override
State<PickPhoto> createState() => _PickPhotoState();
}
class _PickPhotoState extends State<PickPhoto> {
#override
Widget build(BuildContext context) {
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
width: 45,
height: 45,
child: ElevatedButton(
onPressed: () async {
var file =
await picker?.pickImage(source: ImageSource.gallery);
setState(() {
myfile.add(File(file!.path));
});
},
child: Text("Add Photo"))),
),
Expanded(
child: ListView.builder(
// physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: myfile.length,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(4),
height: 175,
width: 125,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
setState(() {
myfile.removeAt(index);
});
},
icon: Icon(Icons.close),
),
),
Expanded(
child: Container(
child: myfile[index] == null
? Text("")
: Image.file(
myfile[index],
fit: BoxFit.fill,
),
),
),
],
),
)),
)
],
),
);
}
}

I cannot fix the buttons in the project

I'm developing an application with flutter. But I cannot fix the buttons in the project. On my chat page, the button goes up. I'm new to the Flutter language, can you help me?
Hello, I'm developing an application with flutter. But I cannot fix the buttons in the project. On my chat page, the button goes up. I'm new to the Flutter language, can you help me?
Screenshot:
My Button Code :
class HomePage extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<HomePage> {
final _scrollController = ScrollController();
List<TabItem> tabItems = List.of([
new TabItem(Icons.home, "Anasayfa", Colors.blue),
new TabItem(Icons.message, "Sohbet Odası", Colors.orange),
new TabItem(Icons.person, "Profil", Colors.red),
]);
int seciliPozisyon = 0;
CircularBottomNavigationController _navigationController;
#override
void initState() {
super.initState();
_navigationController =
new CircularBottomNavigationController(seciliPozisyon);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
centerTitle: true,
title: Text("Crypto App"),
),
body: Stack(
children: <Widget>[
Padding(
child: bodyContainer(),
padding: EdgeInsets.only(bottom: 60),
),
Align(alignment: Alignment.bottomCenter, child: bottomNav())
],
),
);
}
Widget bodyContainer() {
String activeUserId =
Provider.of<AuthorizationService>(context, listen: false).activeUserId;
Color selectedColor = tabItems[seciliPozisyon].color;
switch (seciliPozisyon) {
case 0:
return HomeScreen();
break;
case 1:
return FriendlyChatApp();
break;
case 2:
return Profile(
profileId: activeUserId,
);
break;
}
}
Widget bottomNav() {
return CircularBottomNavigation(
tabItems,
controller: _navigationController,
barHeight: 60,
barBackgroundColor: Colors.white,
animationDuration: Duration(milliseconds: 300),
selectedCallback: (int selectedPos) {
setState(() {
seciliPozisyon = selectedPos;
});
},
);
}
}
ChatApp Code :
void main() {
runApp(
FriendlyChatApp(),
);
}
final ThemeData kIOSTheme = ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.grey[100],
primaryColorBrightness: Brightness.light,
);
final ThemeData kDefaultTheme = ThemeData(
primarySwatch: Colors.orange,
accentColor: Colors.orangeAccent,
);
String _name = '';
class FriendlyChatApp extends StatelessWidget {
const FriendlyChatApp({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ChatScreen(),
);
}
}
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.animationController});
final String text;
final AnimationController animationController;
#override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor:
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
axisAlignment: 0.0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(right: 16.0),
child: CircleAvatar(child: Text(_name[0])),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_name, style: Theme.of(context).textTheme.headline4),
Container(
margin: EdgeInsets.only(top: 5.0),
child: Text(text),
),
],
),
),
],
),
),
);
}
}
class ChatScreen extends StatefulWidget {
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
final List<ChatMessage> _messages = [];
final _textController = TextEditingController();
final FocusNode _focusNode = FocusNode();
bool _isComposing = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: Theme.of(context).platform == TargetPlatform.iOS //new
? BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey[200]),
),
)
: null,
child: Column(
children: [
Flexible(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
),
),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
],
),
),
);
}
Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).accentColor),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Flexible(
child: TextField(
controller: _textController,
onChanged: (String text) {
setState(() {
_isComposing = text.isNotEmpty;
});
},
onSubmitted: _isComposing ? _handleSubmitted : null,
decoration: InputDecoration.collapsed(
hintText: 'Mesajınızı Buraya Yazınız:'),
focusNode: _focusNode,
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: Theme.of(context).platform == TargetPlatform.iOS
? CupertinoButton(
onPressed: _isComposing
? () => _handleSubmitted(_textController.text)
: null,
child: Text('Gönder'),
)
: IconButton(
icon: const Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_textController.text)
: null,
))
],
),
),
);
}
void _handleSubmitted(String text) {
_textController.clear();
setState(() {
_isComposing = false;
});
var message = ChatMessage(
text: text,
animationController: AnimationController(
duration: const Duration(milliseconds: 700),
vsync: this,
),
);
setState(() {
_messages.insert(0, message);
});
_focusNode.requestFocus();
message.animationController.forward();
}
#override
void dispose() {
for (var message in _messages) {
message.animationController.dispose();
}
super.dispose();
}
}
You need to add :
resizeToAvoidBottomInset: false,
Here as shown:
Scaffold(
//here
resizeToAvoidBottomInset: false,
appBar: AppBar(
backgroundColor: Colors.black,
centerTitle: true,
title: Text("Crypto App"),
),
body: Stack(
children: <Widget>[
Padding(
child: bodyContainer(),
padding: EdgeInsets.only(bottom: 60),
),
Align(alignment: Alignment.bottomCenter, child: bottomNav())
],
),
);
}
Here using this whenever user will type something setting value to false will make keyboard overlap the bottom navigation bar.
Hope this is what you wanted to achieve.

Overlay pinched image above everything

I'm trying to overlay an image during max scaling (I'm using the class InteractiveViewer) on top of other objects (also the status bar). Basically like on Instagram. I couldn't find anything reading the docs. A hint on how to proceed?
child: InteractiveViewer(
transformationController: controller,
maxScale: 2.0,
minScale: 2.0,
child: imageBig,
fit: BoxFit.fitWidth,
),
According to this issue on flutter repository:
https://github.com/flutter/flutter/issues/66111
You can achive that by using OverlayEntry Class, which will handle the rendering of your InteractiveViewer child widget over the other widgets.
Also, you can find here a code snippet for InteractiveViewerOverlay widget, that you can use directly inside your project.
https://gist.github.com/zzterrozz/623531eef065a31470e85175c744c986
created by:
https://github.com/PixelToast
https://github.com/zzterrozz
Edited:
Here is an example for the InteractiveViewerOverlay widget and how to use it.
First, the InteractiveViewerOverlay widget
class InteractiveViewerOverlay extends StatefulWidget {
final Widget child;
final double maxScale;
const InteractiveViewerOverlay({
Key key,
#required this.child,
this.maxScale,
}) : super(key: key);
#override
_InteractiveViewerOverlayState createState() =>
_InteractiveViewerOverlayState();
}
class _InteractiveViewerOverlayState extends State<InteractiveViewerOverlay>
with SingleTickerProviderStateMixin {
var viewerKey = GlobalKey();
Rect placeholder;
OverlayEntry entry;
var controller = TransformationController();
Matrix4Tween snapTween;
AnimationController snap;
#override
void initState() {
super.initState();
snap = AnimationController(vsync: this);
snap.addListener(() {
if (snapTween == null) return;
controller.value = snapTween.evaluate(snap);
if (snap.isCompleted) {
entry.remove();
entry = null;
setState(() {
placeholder = null;
});
}
});
}
#override
void dispose() {
snap.dispose();
super.dispose();
}
Widget buildViewer(BuildContext context) {
return InteractiveViewer(
key: viewerKey,
transformationController: controller,
panEnabled: false,
maxScale: widget.maxScale ?? 2.5,
child: widget.child,
onInteractionStart: (details) {
if (placeholder != null) return;
setState(() {
var renderObject =
viewerKey.currentContext.findRenderObject() as RenderBox;
placeholder = Rect.fromPoints(
renderObject.localToGlobal(Offset.zero),
renderObject
.localToGlobal(renderObject.size.bottomRight(Offset.zero)),
);
});
entry = OverlayEntry(
builder: (context) {
return Positioned.fromRect(
rect: placeholder,
child: buildViewer(context),
);
},
);
Overlay.of(context).insert(entry);
},
onInteractionEnd: (details) {
snapTween = Matrix4Tween(
begin: controller.value,
end: Matrix4.identity(),
);
snap.value = 0;
snap.animateTo(
1,
duration: Duration(milliseconds: 250),
curve: Curves.ease,
);
});
}
#override
Widget build(BuildContext context) {
var viewer = placeholder != null
? SizedBox.fromSize(size: placeholder.size)
: buildViewer(context);
return Container(
child: viewer,
);
}
}
Next, An example of implementing the InteractiveViewerOverlay widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(),
body: ListView(children: [
Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
border:
Border(bottom: BorderSide(color: Colors.green))),
width: double.infinity,
height: 60,
child: Column(children: [
Text('Abdelazeem Kuratem',
style: TextStyle(color: Colors.black)),
Text('5 min', style: TextStyle(color: Colors.black)),
])),
InteractiveViewerOverlay(
child: Image.network(
"https://upload.wikimedia.org/wikipedia/commons/6/6a/Mona_Lisa.jpg",
fit: BoxFit.contain,
),
),
Container(
decoration: BoxDecoration(
color: Colors.grey[50],
border: Border(top: BorderSide(color: Colors.green))),
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_createBottomButton(
text: 'Like',
icon: Icons.thumb_up,
onPressed: () {}),
_createBottomButton(
text: 'Comment',
icon: Icons.comment,
onPressed: () {}),
_createBottomButton(
text: 'Share',
icon: Icons.share,
onPressed: () {}),
],
),
],
),
),
],
),
])),
);
}
Widget _createBottomButton({
String text,
IconData icon,
Null Function() onPressed,
}) {
return FlatButton.icon(
onPressed: onPressed,
icon: Icon(
icon,
color: Colors.green,
size: 21,
),
label: Text(
text,
style: TextStyle(color: Colors.green, fontSize: 14),
),
);
}
}

Listview scrolling and selecting Textfield afterwards is freezing my app

I am using the package
country_code_picker: ^1.4.0
https://pub.dev/packages/country_code_picker#-installing-tab-
with flutter 1.17.3
Which is pretty much one of the only country code picker packages. But I have one serious problem an I don't have a clue what it could be.
When I run this code
import 'package:flutter/material.dart';
import 'package:country_code_picker/country_code_picker.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
App();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: TestWidget(),
);
}
}
class TestWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(body: _buildCountryPicker(context));
}
Widget _buildCountryPicker(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Center(
child: CountryCodePicker(
initialSelection: 'NL',
),
),
);
}
}
And I open the dialog to select a country. I scroll in the list and then select the TextField my keyboard opens and when I try to type something my entire app freezes. I can't even hot reload. I don't get a single error.
I am running this on my Huawei P30, but I also experience this on other android devices. I don't know if this is a flutter bug or a country code picker bug.
I think it is probably in this widget somewhere. If anyone could point me in the right direction it would help me alot!
class SelectionDialog extends StatefulWidget {
final List<CountryCode> elements;
final bool showCountryOnly;
final InputDecoration searchDecoration;
final TextStyle searchStyle;
final TextStyle textStyle;
final WidgetBuilder emptySearchBuilder;
final bool showFlag;
final double flagWidth;
final Size size;
final bool hideSearch;
/// elements passed as favorite
final List<CountryCode> favoriteElements;
SelectionDialog(
this.elements,
this.favoriteElements, {
Key key,
this.showCountryOnly,
this.emptySearchBuilder,
InputDecoration searchDecoration = const InputDecoration(),
this.searchStyle,
this.textStyle,
this.showFlag,
this.flagWidth = 32,
this.size,
this.hideSearch = false,
}) : assert(searchDecoration != null, 'searchDecoration must not be null!'),
this.searchDecoration =
searchDecoration.copyWith(prefixIcon: Icon(Icons.search)),
super(key: key);
#override
State<StatefulWidget> createState() => _SelectionDialogState();
}
class _SelectionDialogState extends State<SelectionDialog> {
/// this is useful for filtering purpose
List<CountryCode> filteredElements;
#override
Widget build(BuildContext context) => SimpleDialog(
titlePadding: const EdgeInsets.all(0),
title: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
IconButton(
padding: const EdgeInsets.all(0),
iconSize: 20,
icon: Icon(
Icons.close,
),
onPressed: () => Navigator.pop(context),
),
if (!widget.hideSearch)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextField(
style: widget.searchStyle,
decoration: widget.searchDecoration,
onChanged: _filterElements,
),
),
],
),
children: [
Container(
width: widget.size?.width ?? MediaQuery.of(context).size.width,
height:
widget.size?.height ?? MediaQuery.of(context).size.height * 0.7,
child: ListView(
children: [
widget.favoriteElements.isEmpty
? const DecoratedBox(decoration: BoxDecoration())
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...widget.favoriteElements.map(
(f) => SimpleDialogOption(
child: _buildOption(f),
onPressed: () {
_selectItem(f);
},
),
),
const Divider(),
],
),
if (filteredElements.isEmpty)
_buildEmptySearchWidget(context)
else
...filteredElements.map(
(e) => SimpleDialogOption(
key: Key(e.toLongString()),
child: _buildOption(e),
onPressed: () {
_selectItem(e);
},
),
),
],
),
),
],
);
Widget _buildOption(CountryCode e) {
return Container(
width: 400,
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
if (widget.showFlag)
Flexible(
child: Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Image.asset(
e.flagUri,
package: 'country_code_picker',
width: widget.flagWidth,
),
),
),
Expanded(
flex: 4,
child: Text(
widget.showCountryOnly
? e.toCountryStringOnly()
: e.toLongString(),
overflow: TextOverflow.fade,
style: widget.textStyle,
),
),
],
),
);
}
Widget _buildEmptySearchWidget(BuildContext context) {
if (widget.emptySearchBuilder != null) {
return widget.emptySearchBuilder(context);
}
return Center(
child: Text('No country found'),
);
}
#override
void initState() {
filteredElements = widget.elements;
super.initState();
}
void _filterElements(String s) {
s = s.toUpperCase();
setState(() {
filteredElements = widget.elements
.where((e) =>
e.code.contains(s) ||
e.dialCode.contains(s) ||
e.name.toUpperCase().contains(s))
.toList();
});
}
void _selectItem(CountryCode e) {
Navigator.pop(context, e);
}
}
Also filed an issue on the flutter github https://github.com/flutter/flutter/issues/59886
Edit:
I have a video of it right here
https://www.youtube.com/watch?v=669KitFG9ek&feature=youtu.be
I just had to remove the keys, so there probably was a duplicate key
...filteredElements.map(
(e) => SimpleDialogOption(
//key: Key(e.toLongString()),
child: _buildOption(e),
onPressed: () {
_selectItem(e);
},
),
),

how to change layout in Flutter

I've been trying to design the layout of my ExpansionTile just like the design below but I couldn't figure out how to change the layout. any suggestion on how to change the border radius, change the background color and also make a gap between each other?.
I tried adding boxDecoration in each container but the style only apply to outside but not on each expansionTile.
import 'package:flutter/material.dart';
class MyReoderWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ReorderItems(topTen: ['j']);
}
}
class DataHolder {
List<String> parentKeys;
Map<String, List<String>> childMap;
DataHolder._privateConstructor();
static final DataHolder _dataHolder = DataHolder._privateConstructor();
static DataHolder get instance => _dataHolder;
factory DataHolder.initialize({#required parentKeys}) {
_dataHolder.parentKeys = parentKeys;
_dataHolder.childMap = {};
for (String key in parentKeys) {
_dataHolder.childMap.putIfAbsent(
}
return _dataHolder;
}
}
class ReorderItems extends StatefulWidget {
final List<String> topTen;
ReorderItems({this.topTen});
#override
_ReorderItemsState createState() => _ReorderItemsState();
}
class _ReorderItemsState extends State<ReorderItems> {
#override
void initState() {
super.initState();
// initialize the children for the Expansion tile
// This initialization can be replaced with any logic like network fetch or something else.
DataHolder.initialize(parentKeys: widget.topTen);
}
#override
Widget build(BuildContext context) {
return PrimaryScrollController(
key: ValueKey(widget.topTen.toString()),
controller: ScrollController(),
child: Container(
decoration: BoxDecoration(),
child: ReorderableListView(
onReorder: onReorder,
children: getListItem(),
),
),
);
}
List<ExpansionTile> getListItem() => DataHolder.instance.parentKeys
.asMap()
.map((index, item) => MapEntry(index, buildTenableListTile(item, index)))
.values
.toList();
ExpansionTile buildTenableListTile(String mapKey, int index) => ExpansionTile(
key: ValueKey(mapKey),
title: Text(mapKey),
leading: Icon(Icons.list),
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20))
),
key: ValueKey('$mapKey$index'),
height: 200,
child: Container(
padding: EdgeInsets.only(left: 30.0),
child: ReorderList(
parentMapKey: mapKey,
),
),
),
],
);
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
setState(() {
String game = widget.topTen[oldIndex];
DataHolder.instance.parentKeys.removeAt(oldIndex);
DataHolder.instance.parentKeys.insert(newIndex, game);
});
}
}
class ReorderList extends StatefulWidget {
final String parentMapKey;
ReorderList({this.parentMapKey});
#override
_ReorderListState createState() => _ReorderListState();
}
class _ReorderListState extends State<ReorderList> {
#override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: ScrollController(),
child: ReorderableListView(
// scrollController: ScrollController(),
onReorder: onReorder,
children: DataHolder.instance.childMap[widget.parentMapKey]
.map(
(String child) => Container(
child: ListTile(
key: ValueKey(child),
leading: Icon(Icons.list),
title: Text(child),
),
),
)
.toList(),
),
);
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
List<String> children = DataHolder.instance.childMap[widget.parentMapKey];
String game = children[oldIndex];
children.removeAt(oldIndex);
children.insert(newIndex, game);
DataHolder.instance.childMap[widget.parentMapKey] = children;
// Need to set state to rebuild the children.
setState(() {});
}
}
You can do it using custom expandable container.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Calendar',
theme: ThemeData(
primarySwatch: Colors.grey,
),
debugShowCheckedModeBanner: false,
home: Material(
child: MyReoderWidget(),
),
);
}
}
class CustomModel {
String title;
bool isExpanded;
List<String> subItems;
CustomModel({this.title, this.subItems, this.isExpanded = false});
}
class MyReoderWidget extends StatefulWidget {
#override
_MyReoderWidgetState createState() => _MyReoderWidgetState();
}
class _MyReoderWidgetState extends State<MyReoderWidget> {
List<CustomModel> listItems;
#override
void initState() {
super.initState();
listItems = List<CustomModel>();
listItems.add(CustomModel(
title: "App Name 1", subItems: ["Card Name 1", "Card Name 2"]));
listItems.add(CustomModel(
title: "App Name 2", subItems: ["Card Name 3", "Card Name 4"]));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: ListView(
children: listItems
.map((model) => new Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: ExpandableCardContainer(
isExpanded: model.isExpanded,
collapsedChild: createHeaderCard(model),
expandedChild: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: createHeaderCard(model),
)
]..addAll(model.subItems
.map((e) => createChildCard(e))
.toList()),
),
),
))
.toList()),
);
}
Widget createHeaderCard(CustomModel model) {
return Container(
child: Row(
children: <Widget>[
Icon(
Icons.more_vert,
color: Colors.white,
),
Expanded(
child: Text(
model.title,
style: TextStyle(color: Colors.white),
),
),
GestureDetector(
onTap: () {
setState(() {
model.isExpanded = !model.isExpanded;
});
},
child: Icon(
model.isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.white,
),
)
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFF132435),
),
height: 50,
);
}
Widget createChildCard(String subItems) {
return Container(
margin: EdgeInsets.only(left: 30, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.more_vert,
color: Colors.white,
),
Expanded(
child: Text(
subItems,
style: TextStyle(color: Colors.white),
),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFF132435),
),
height: 50,
);
}
}
class ExpandableCardContainer extends StatefulWidget {
final bool isExpanded;
final Widget collapsedChild;
final Widget expandedChild;
const ExpandableCardContainer(
{Key key, this.isExpanded, this.collapsedChild, this.expandedChild})
: super(key: key);
#override
_ExpandableCardContainerState createState() =>
_ExpandableCardContainerState();
}
class _ExpandableCardContainerState extends State<ExpandableCardContainer> {
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: widget.isExpanded ? widget.expandedChild : widget.collapsedChild,
);
}
}