Flutter scrollable layout with dynamic child - flutter

I want to create a generic Layout which accepts a child Widget as a parameter, that lays out the content as follows:
I have an AppBar at the Top, a Title (headline), and below that the Content (could be anything). At the bottom, I have a Column with a few buttons. If the content is too big for the screen, all those widgets, except the AppBar, are scrollable. If the content fits the screen, the title and content should be aligned at the top, and the buttons at the bottom.
To showcase what I mean, I created a drawing:
It is easy to create to scrollable content functionality. But I struggle with laying out the content so that the buttons are aligned at the bottom, if the content does NOT need to be scrollable.
It is important to say that I don't know the height of the content widget or the buttons. They are dynamic and can change their height. Also, the title is optional and can have two different sizes.
What I tried is the following:
import 'package:flutter/material.dart';
class BaseScreen extends StatelessWidget {
final String? title;
final bool bigHeader;
final Widget child;
final Widget bottomButtons;
const BaseScreen({
Key? key,
required this.child,
required this.bottomButtons,
this.bigHeader = true,
this.title,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final AppBar appBar = AppBar(
title: Text("AppBar"),
);
double minChildHeight = MediaQuery.of(context).size.height -
MediaQuery.of(context).viewInsets.bottom -
MediaQuery.of(context).viewInsets.top -
MediaQuery.of(context).viewPadding.bottom -
MediaQuery.of(context).viewPadding.top -
appBar.preferredSize.height;
if (title != null) {
minChildHeight -= 20;
if (bigHeader) {
minChildHeight -= bigHeaderStyle.fontSize!;
} else {
minChildHeight -= smallHeaderStyle.fontSize!;
}
}
final Widget content = Column(
mainAxisSize: MainAxisSize.min,
children: [
if (title != null)
Text(
title!,
style: bigHeader ? bigHeaderStyle : smallHeaderStyle,
textAlign: TextAlign.center,
),
if (title != null)
const SizedBox(
height: 20,
),
ConstrainedBox(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
child,
bottomButtons,
],
),
constraints: BoxConstraints(
minHeight: minChildHeight,
),
),
],
);
return Scaffold(
appBar: appBar,
body: SingleChildScrollView(
child: content,
),
);
}
TextStyle get bigHeaderStyle {
return TextStyle(fontSize: 20);
}
TextStyle get smallHeaderStyle {
return TextStyle(fontSize: 16);
}
}
The scrolling effects work perfectly, but the Buttons are not aligned at the bottom. Instead, they are aligned directly below the content. Does anyone know how I can fix this?

DartPad you can check here
customscrollview tutorial
Scaffold(
// bottomNavigationBar: ,
appBar: AppBar(
title: Text(" App Bar title ${widgets.length}"),
),
//============
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
// controller: _mycontroller,
children: [
title,
...contents,
// ---------------------This give Expansion and button get down --------
Expanded(
child: Container(),
),
// ---------------------This give Expansion and button get down --------
Buttons
],
),
)
],
))
We can Achieve with the help of CustomScrollView widget and Expanded widget.here Expanded widget just expand between the widget
Sample Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),
);
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var widgets = [];
var _mycontroller = ScrollController();
#override
Widget build(BuildContext context) {
var title = Center(
child: Text(
"Scrollable title ${widgets.length}",
style: TextStyle(fontSize: 30),
));
var contents = [
...widgets,
];
var Buttons = Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
child: ElevatedButton(
onPressed: () {
setState(() {
widgets.add(Container(
height: 100,
child: ListTile(
title: Text(widgets.length.toString()),
subtitle: Text("Contents BTN1"),
),
));
});
// _mycontroller.jumpTo(widgets.length * 100);
},
child: Text("BTN1"),
),
),
)),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
child: ElevatedButton(
onPressed: () {
setState(() {
if (widgets.length > 0) {
widgets.removeLast();
}
});
// _mycontroller.jumpTo(widgets.length * 100);
},
child: Text("BTN2"),
),
),
))
],
);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
// bottomNavigationBar: ,
appBar: AppBar(
title: Text(" App Bar title ${widgets.length}"),
),
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
// controller: _mycontroller,
children: [
title,
...contents,
Expanded(
child: Container(),
),
Buttons
],
),
)
],
)),
),
);
}
}

Try this:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BaseScreen(
bottomButtons: [
ElevatedButton(onPressed: () {}, child: const Text('Button 1')),
ElevatedButton(onPressed: () {}, child: const Text('Button 2')),
],
content: Container(
color: Colors.lightGreen,
height: 200,
),
title: 'Title',
),
);
}
}
class BaseScreen extends StatelessWidget {
final bool bigHeader;
final List<Widget> bottomButtons;
final String? title;
final Widget content;
const BaseScreen({
this.bigHeader = true,
required this.bottomButtons,
required this.content,
this.title,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AppBar'),
),
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: [
if (title != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
title!,
style: bigHeader ? _bigHeaderStyle : _smallHeaderStyle,
textAlign: TextAlign.center,
),
),
content,
const Spacer(),
...bottomButtons,
],
),
),
],
),
);
}
TextStyle get _bigHeaderStyle => const TextStyle(fontSize: 20);
TextStyle get _smallHeaderStyle => const TextStyle(fontSize: 16);
}
Screenshots:
without_scrolling
scrolled_up
scrolled_down

Related

How do I fix my problem with routes in Flutter?

good evening. I am currently doing a To-do List in Flutter and I want to pass the Title of my List and the Description of my List when I click on a new screen but upon setting up Routes and and declaring the values on my next, it shows the "2 positional arguments expected, but 0 found" on the routes I've set up. Here are my codes:
Here is my 1st screen:
import 'package:flutter/material.dart';
import 'package:todo_list/details.dart';
import 'package:todo_list/note.dart';
class MyApp extends StatelessWidget {
final String text;
final int number;
final String listDescription;
const MyApp(
{super.key,
required this.text,
required this.number,
required this.listDescription});
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
DetailsPage.routeName: (ctx) => DetailsPage(),
},
home: CustomListTile(
text: text,
number: number,
listDescription: listDescription,
),
);
}
}
class CustomListTile extends StatelessWidget {
final String text;
final int number;
final String listDescription;
const CustomListTile(
{super.key,
required this.text,
required this.number,
required this.listDescription});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Navigator.pushNamed(context, DetailsPage.routeName,
arguments: Note(title: text, description: listDescription));
},
/* onTap: () {
Widget okButton = TextButton(
child: const Text("CLOSE"),
onPressed: () {
Navigator.of(context).pop();
},
);
AlertDialog alert = AlertDialog(
title: Text(text),
content: Text('This item in the list contains $listDescription'),
actions: [
okButton,
]);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
});
}, */
child: Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("$number. $text",
style: const TextStyle(
fontSize: 20,
)),
const Icon(Icons.chevron_right)
],
),
Text(
listDescription,
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
const Divider()
],
),
),
);
}
}
and here is my 2nd screen:
import 'package:flutter/material.dart';
import 'note.dart';
class DetailsPage extends StatefulWidget {
static const String routeName = "/details";
final String text;
final String listDescription;
const DetailsPage(this.text, this.listDescription, {super.key});
#override
State<DetailsPage> createState() => _DetailsPageState();
}
class _DetailsPageState extends State<DetailsPage> {
late Note params;
#override
void didChangeDependencies() {
params = ModalRoute.of(context)!.settings.arguments! as Note;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
Widget titleSection = Container(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(bottom: 0),
child: Text(
params.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
),
],
),
),
],
),
);
Color color = Theme.of(context).primaryColor;
Widget buttonSection = Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButtonColumn(
color,
Icons.edit,
'EDIT',
),
_buildButtonColumn(color, Icons.delete, 'DELETE'),
],
);
Widget textSection = Padding(
padding: const EdgeInsets.all(20),
child: Text(
params.description,
softWrap: true,
),
);
return MaterialApp(
title: 'Layout for a New Screen',
theme: ThemeData(
primarySwatch: Colors.brown,
),
home: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
title: Text(params.title),
),
body: ListView(
children: [
Image.asset(
'lib/images/placeholder.jpg',
width: 600,
height: 240,
fit: BoxFit.cover,
),
titleSection,
buttonSection,
textSection,
],
),
),
);
}
Column _buildButtonColumn(
Color color,
IconData icon,
String label,
) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
}
/* return Scaffold(
appBar: AppBar(title: Text(text)),
body: Center(
child: Row(
children: [Text(description)],
),
));
}
} */
How do I make it so that the data I'll pass such as the Title and the Description will appear on the 2nd screen without the error "2 positional argument(s) expected, but 0 found.
Try adding the missing arguments." appearing.
I tried the Quick Fixes on VS Code such as adding a const modifier but I think the const modifier wouldn't do a fix since both data I'm trying to pass are dynamic and may change from time to time.
As you've define details page
class DetailsPage extends StatefulWidget {
static const String routeName = "/details";
final String text;
final String listDescription;
const DetailsPage(this.text, this.listDescription, {super.key});
You need to pass two string as positional argument.
So it can be
routes: {
DetailsPage.routeName: (ctx) => DetailsPage("text","description"),
},
also while you are using route arguments, you can remove theses from widget class and just accept from state class context with ModalRoute.
You can check this example and development/ui/navigation .

how to check which element was tapped in flutter and dat

I created a list of widgets. What I would like to do is to get the index of the widget that is being tapped. I wrapped the widget in gesture detection, and I would like to print the index of the widget which is being tapped
This is a widget I created:
class Tile extends StatelessWidget {
final Color color;
const Tile({Key? key,required this.color}) : super(key: key);
sayhello(int idd){
print("Hello from the tile file$idd");
}
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
width: 30,
height: 31,
color: color,
),
);
}
}
This is the file where it is being used:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<Widget> list = [
Tile(color: Colors.black26),
Tile(color: Colors.cyanAccent),
Tile(color: Colors.deepOrange),
Tile(color: Colors.tealAccent),
Tile(color: Colors.purpleAccent),
Tile(color: Colors.yellowAccent),
Tile(color: Colors.black),
];
Color color=Colors.amber;
void _incrementCounter() {
setState(() {
if(true)
{
list[0]=Container(
child: Text('HN'),
color: Colors.deepOrange,
);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children:[
Row(
children: [
list[0],
list[1],
list[2],
],
),
Row(
children: [
list[3],
list[4],
list[5],
],
),
]
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
I created a list of my widget and added the gestureDetector. I want to get the index of the widget which is being tapped.
I recommend you to use widget key.
Then you can print it on widget tap.
Code:
Tile widget
.
GestureDetector(
onTap: () {
print(
key.toString(),
);
},
child: Container(
width: 30,
height: 31,
color: color,
),
),
Calling Tile widget
List<Widget> list = [
Tile(
color: Colors.black26,
key: Key('tile_black'),
),
Tile(
color: Colors.cyanAccent,
key: Key('cyan_accent'),
),
];

Change the color of a container based on the position of a SingleChildScrollView in Flutter

I have an app that shows its content in a SingleChildScrollView. There is Container with a transparent color that I'd like to change the color of to red when the SingleChildScrollView is scrolled to any other position than the start position and then change the color back to transparent when the SingleChildScrollView is scrolled back to its starting position. Code:
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Column(
children: [
Flexible(
child: ScrollConfiguration(
behavior: RemoveScrollGlow(),
child: SingleChildScrollView(
child: Column(
children: [
Stack(...) //This is the top section of the page
],
),
),
),
),
],
),
Container(
color: Colors.transparent, //This is the Color I want to change based on the position of the SingleChildScrollView
height: 120,
child: Column(...)
),
],
),
backgroundColor: Colors.white,
);
}
}
EDIT: I managed to make it work by wrapping the SingleChildScrollView in a NotificationListener and updating the color based on the notification like this:
class _AppState extends State<App> {
Color bannercolor = Colors.transparent;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Column(
children: [
Flexible(
child: ScrollConfiguration(
behavior: RemoveScrollGlow(),
child: NotificationListener<ScrollUpdateNotification>(
onNotification: (scrollEnd) {
final metrics = scrollEnd.metrics;
if (metrics.pixels != 0) {
setState(() {
bannercolor = Colors.white;
});
} else {
setState(() {
bannercolor = Colors.transparent;
});
}
return true;
},
child: SingleChildScrollView(
child: Column(
children: [
Column(...),
],
),
),
),
),
),
],
),
Container(
color: bannercolor,
height: 120,
child: Column(...),
),
],
),
backgroundColor: Colors.white,
);
}
}
You can try listening to the scroll controller offset like this
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
final ScrollController _scrollController = ScrollController ();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Column(
children: [
Flexible(
child: ScrollConfiguration(
behavior: RemoveScrollGlow(),
child: SingleChildScrollView(
controller: _scrollController, //add controller here
child: Column(
children: [
Stack(...) //This is the top section of the page
],
),
),
),
),
],
),
AnimatedBuilder(
        animation: _scrollController,
        builder: (context, _content) {
          return  Container (
(_scrollController.offset>20)? Colors.blue: Colors.transparent,
height: 120,
child: Column(...)
);
}
),
],
),
backgroundColor: Colors.white,
);
}
}

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

Can't make bottom textfield stick on top of keyboard when it show up in chat app, Flutter

So I m developing a chat app which read and write data from firebase.
I have a streambuilder(that shows the messages)which is above a Container widget(which hold the input text field)
My problem is when I tap the input field and the keyboard pop ups, it cover the message textfield.
I have done many solutions from Stackoverflow and none of them seems to work in my case. The technique i have tried are
-resizeToAvoidBottomInset: true
-Expanded(when I try this the messages no longer show up)
-Flexible
I test the same code in my other project and it works. The text field stick on top of the keyboard. It just doesn't work in a particular project which use Bloc Pattern. There might have been some scaffold error or I don't know. Please help
import 'chat_design.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final _firestore = Firestore.instance;
FirebaseUser loggedInUser;
class ChatScreen extends StatefulWidget {
static const String id = 'chat_screen';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final messageTextController = TextEditingController();
final _auth = FirebaseAuth.instance;
String messageText;
#override
void initState() {
// TODO: implement initState
super.initState();
getCurrentUser();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
print(loggedInUser.email);
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
_auth.signOut();
Navigator.pop(context);
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessagesStream(),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
//Do something with the user input.
messageText = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
messageTextController.clear();
//Implement send functionality.
_firestore.collection('messages').add({
'text': messageText,
'sender': loggedInUser.email,
});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').limit(100).snapshots(),
builder: (context, snapshot) {
//wait before data is loaded
if(snapshot.data == null) return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(strokeWidth: 7,),
),
],
);
final messages = snapshot.data.documents.reversed;
List<MessageBubble> messageBubbles = [];
for (var message in messages) {
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final currentUser = loggedInUser.email;
final messageBubble = MessageBubble(
sender: messageSender,
text: messageText,
isMe :currentUser == messageSender,
);
messageBubbles.add(messageBubble);
}
return Expanded(
child: ListView(
reverse: true,
padding:
EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
children: messageBubbles,
),
);
},
);
}
}
class MessageBubble extends StatelessWidget {
MessageBubble({this.sender, this.text,this.isMe});
final String sender;
final String text;
final bool isMe;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Text(
sender,
style: TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
Material(
borderRadius: isMe ? BorderRadius.only(topLeft: Radius.circular(30.0),
bottomLeft: Radius.circular(30.0),
bottomRight: Radius.circular(15.0))
:BorderRadius.only(topRight: Radius.circular(30.0),
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(30.0)),
color: isMe ? Colors.lightBlueAccent: Colors.white,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
child: Text(
'$text',
style: TextStyle(
color: isMe ? Colors.white : Colors.black,
fontSize: 15.0,
),
),
),
),
],
),
);
}
}
Take ListView or SingleChildScrollView under Body. And then use bottomNavigationBar in Scaffold.
Scaffold(
body: ListView(
children: [],
),
bottomNavigationBar: Container(
padding: MediaQuery.of(context).viewInsets,
color: Colors.grey[300],
child: Container(
padding: EdgeInsets.symmetric(vertical: 2),
margin: EdgeInsets.symmetric(horizontal: 5),
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Type a message',
),
))
),
);
I think what you need is use a SingleChildScrollView, the content will scroll when the keyboard show up, will let you two examples:
// Flutter code sample for
// In this example, the children are spaced out equally, unless there's no more
// room, in which case they stack vertically and scroll.
//
// When using this technique, [Expanded] and [Flexible] are not useful, because
// in both cases the "available space" is infinite (since this is in a viewport).
// The next section describes a technique for providing a maximum height constraint.
import 'package:flutter/widgets.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WidgetsApp(
title: 'Flutter Code Sample',
builder: (BuildContext context, Widget navigator) {
return MyStatelessWidget();
},
color: const Color(0xffffffff),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar(
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
// A fixed-height child.
color: const Color(0xff808000), // Yellow
height: 120.0,
),
Container(
// Another fixed-height child.
color: const Color(0xff008000), // Green
height: 120.0,
),
],
),
),
),
);
},
);
}
}
Another example for SingleChildScrollView
// Flutter code sample for
// In this example, the column becomes either as big as viewport, or as big as
// the contents, whichever is biggest.
import 'package:flutter/widgets.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WidgetsApp(
title: 'Flutter Code Sample',
builder: (BuildContext context, Widget navigator) {
return MyStatelessWidget();
},
color: const Color(0xffffffff),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
Container(
// A fixed-height child.
color: const Color(0xff808000), // Yellow
height: 120.0,
),
Expanded(
// A flexible child that will grow to fit the viewport but
// still be at least as big as necessary to fit its contents.
child: Container(
color: const Color(0xff800000), // Red
height: 120.0,
),
),
],
),
),
),
);
},
);
}
}
Try this: Go to your AndroidManifest.xml and remove:
android:windowSoftInputMode="adjustResize" under the application-activity tag.
Basically, just change this:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
to this:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
>
Worked for one of my apps.
This is the correct way to do this:
Widget _buildContent(BuildContext context) {
return Stack(
children: [
Column(
children: [
Expanded(
child: YOUR_SCROLLING_AREA_HERE,
),
YOUR_PINNED_WIDGET_HERE,
],
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: _buildContent(context),
);
}