Related
I am trying to use the BLoC pattern with RxDart, and i would like to get an event from a list through StreamBuilder.
My problem is that when I am opening my Details Screen, for like 1sec I've got the following error message : The method 'firstWhere' was called on null.
Is there a way to wait getting data without adding a loading progress because I would like to keep my HeroAnimation on my image ?
class _EventsDetailsScreenState extends State<EventsDetailsScreen> {
#override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
//Event event = eventBloc.events.firstWhere((elmt) => elmt.id == widget.id, orElse:() => null);
return StreamBuilder(
stream : eventBloc.allEvents,
builder: (context, snapshot){
final Event event = snapshot.data.firstWhere((elmt) => elmt.id == widget.id, orElse:() => null);
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context, event),
(event.description != null && event.description.trim().isNotEmpty) ? _buildDescriptionSection(context, event) : Container(),
],
))
]),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.create), onPressed: () async => print('Edit')),
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
},
);
}
Widget _buildEventImage(BuildContext context, Event event) {
return Hero(
tag: 'eventImg${event.id}',
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/croatia.jpg"),
alignment: Alignment.topCenter,
fit: BoxFit.fill),
),
padding: EdgeInsets.only(left: 40.0, bottom: 250.0),
alignment: Alignment.bottomLeft,
height: MediaQuery.of(context).size.height - 30.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Material(
color: Colors.transparent,
child: Text(
event.name,
style: eventNameTextStyle,
)),
),
Container(
child: Material(
color: Colors.transparent,
child: Text(
"In ${event.date.difference(DateTime.now()).inDays} days",
style: eventDateTextStyle)))
],
)),
);
}
I hope you have solved this already ,
but I faced the same problem while working with BLoC Pattern in Flutter.
The problem comes from the fact that streams are asynchronous and the build method is synchronous .
This is from the official docs :
The initial snapshot data can be controlled by specifying initialData.
This should be used to ensure that the first frame has the expected
value, as the builder will always be called before the stream listener
has a chance to be processed.
StreamBuilder class
So in order to avoid this behavior you have to use initialData property in the StreamBuilder.
To use this with a BLoC you could expose a simple getter at your BLoC that get synchronous value of your current state to give as an initial data for any new subscriber.
You can add an if statement to check if the snapshot has data, this way firstWhere function will only be called if there's data in the snapshot :
class _EventsDetailsScreenState extends State<EventsDetailsScreen> {
#override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
//Event event = eventBloc.events.firstWhere((elmt) => elmt.id == widget.id, orElse:() => null);
return StreamBuilder(
stream : eventBloc.allEvents,
builder: (context, snapshot){
if(snapshot.hasData){
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context, event),
(event.description != null && event.description.trim().isNotEmpty) ? _buildDescriptionSection(context, event) : Container(),
],
))
]),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.create), onPressed: () async => print('Edit')),
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
}else{
return Scaffold();
}
});
}
I currently have a listview operating on the whole of my screen. I would like to have a button in the bottom of the screen, thus splitting it up so the listview doens't fill up the whole of my window.
This is the current code building the class:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HT scoreboard'),
),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Spillere').orderBy("score", descending: true).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 10.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name + ": " + record.score.toString()),
trailing: new IconButton(icon: new Icon(isAdmin ? Icons.add : null, color: Colors.green),
onPressed: (){
if(isAdmin){
record.reference.updateData({'score': record.score + 1});
}
}
),
),
),
);
change your buildlist function to include a column with the button and listview as children
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return Column(
children:[
Expanded(
child: ListView(
padding: const EdgeInsets.only(top: 10.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
),
),
RaisedButton(
// fill in required params
)
])
}
To prevent the buttons being pushed above the keyboard;
return CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// list items
],
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
RaisedButton()
],
),
)
],
);
I am trying to expand widget inside of Column widget but not able to make it expended.
When giving constant height to parent widget, the layout will be rendered as expected. But as I remove the constant height layout is not as expected as I want to make Listview with it and I should not give a constant height to the widget which will be used as listview item.
Below is my layout code.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell() {
return Container(
color: Colors.yellow,
// height: 200, after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child:Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: Center(
child: ListView(
children: <Widget>[
cell(),
],
)
),
);
}
}
Below is my expected output screenshot.
Try to wrap your Container with IntrinsicHeight
return IntrinsicHeight(
Container(
color: Colors.yellow
child: ...
)
)
Your ListView needs to be inside Flexible. Flexible inside Column will set maximum height available to ListView. ListView needs a finite height from parent, Flexible will provide that based on max. space available.
Column(
children: <Widget>[
Flexible(
child: ListView.builder(...)
),
Container(
color: Colors.red,
child:Text('apple 2'),
),
],
)
A nice way of doing this, it's to play with the MediaQuery, heigth and width.
Let me explain, if you want the widget to have the maximum heigth of a decide screen, you can set it like this:
Container(
height: MediaQuery.of(context).size.height // Full screen size
)
You can manipulate it by dividing by 2, 3, 400, the value you want.
The same things works for the width
Container(
width: MediaQuery.of(context).size.width // Can divide by any value you want here
)
Actually quite the opposite, if you're planning to use this as an item in a listViewyou can't let infinite size on the same axis your listView is scrolling.
Let me explain:
Currently you're not defining any height on your cell() widget, which is fine if you're using it alone. like this :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell() {
return Container(
color: Colors.yellow,
//height: 250, after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child: Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: cell(),
);
}
}
But using it with a listView you have to define a height. A listView scrolls as long as it have some content to scroll. Right now it just like you're giving it infinite content so it would scroll indefinitely. Instead Flutter is not constructing it.
It's actually quite ok to define a global size for your container (as an item). You can even define a specific size for each using a parameter like this :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell(double height) {
return Container(
color: Colors.yellow,
height: height, //after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child: Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: ListView(
children: <Widget>[
cell(250.0),
cell(230.0),
cell(300.0),
],
)
);
}
}
I have an OverlayEntry that displays fullscreen. I want to dispatch an actions an close it onTap of the overlayentry's buttons
OverlayEntry _buildOverlayFeedback(BuildContext context, String tituloEvento) {
return OverlayEntry(
builder: (context) => Material(
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
ListTile(
leading: Icon(Icons.sentiment_dissatisfied),
title: Text('No me ha gustado'),
onTap: () {
// how to close myself????
},
),
ListTile(
leading: Icon(Icons.sentiment_very_satisfied),
title: Text('Muy bien'),
onTap: () {}),
],
),
],
),
),
),
);
}
You call remove() on the OverlayEntry itself.
This could be one way of doing it:
OverlayEntry _buildOverlayFeedback(BuildContext context, String tituloEvento) {
OverlayEntry entry;
entry = OverlayEntry(
builder: (context) => Material(
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
ListTile(
leading: Icon(Icons.sentiment_dissatisfied),
title: Text('No me ha gustado'),
onTap: () {
entry.remove();
},
),
ListTile(
leading: Icon(Icons.sentiment_very_satisfied),
title: Text('Muy bien'),
onTap: () {}),
],
),
],
),
),
),
);
return entry;
}
For those of you who are trying to figure out how to do this with FCM onMessage with Navigator.of(context).overlay.insert(entry) - chemamolins' answer works, you just have to adapt it slightly. Here's a similar example to get you started:
onMessage: (Map<String, dynamic> message) async {
OverlayEntry entry;
entry = OverlayEntry(builder: (context) {
return GestureDetector(
onTap: entry.remove,
child: SafeArea(
child: Align(
alignment: Alignment.topCenter,
child: Material(
type: MaterialType.transparency,
child: Container(
decoration: BoxDecoration(color: Colors.white),
width: double.infinity,
height: 60,
child: Column(
children: <Widget>[
Text(message['notification']['title']),
Text(message['notification']['body']),
],
),
),
),
),
),
);
});
Navigator.of(context).overlay.insert(entry);
},
And onTap will close out the OverlayEntry.
I am building a quiz app which reveals the explanation for the correct answer after the user submits their chosen answer.
There are two buttons on the layout -- "Next Question" & "Submit Answer."
In the initial state, the "Next Question" button is subtle as it is not clickable and only the "Submit Answer" buttons is clickable.
Click Here to View the Layout of the Initial State
When the "Submit Answer" button is clicked, two actions should happen:
1. The "Submit Answer" button then becomes subtle and not clickable and the "Next Question" button becomes bold and vibrant and, of course, clickable.
2. Also, below the row of the two buttons, an additional section appears (another container maybe, i don't know) revealing the explanation for the correct answer.
I'd like some help in implementing the above two actions
So far, this is the code that I have:
Widget nextQuestion = new RaisedButton(
padding: const EdgeInsets.all(10.0),
child: const Text('Next Question'),
color: Color(0xFFE9E9E9),
elevation: 0.0,
onPressed: () {
null;
},
);
Widget submitAnswer = new RaisedButton(
padding: const EdgeInsets.all(10.0),
child: const Text('Submit Answer'),
color: Color(0xFFE08284),
elevation: 5.0,
onPressed: () {
null;
},
);
return Scaffold(
body: new CustomScrollView(
slivers: <Widget>[
new SliverPadding(
padding: new EdgeInsets.all(0.0),
sliver: new SliverList(
delegate: new SliverChildListDelegate([
new Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: <Widget>[nextQuestion, submitAnswer]),
new SizedBox(height: 50.0),
]),
),
),
],
),
);
you can implement using setState method.
i implement something like that just go through that.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Demo',
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
));
}
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
int submit = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
new RaisedButton(
padding: const EdgeInsets.all(10.0),
child: const Text('Next Question'),
color: submit == 0 ? Color(0xFFE9E9E9) : Colors.grey,
elevation: 0.0,
onPressed: () {
submit == 0 ? null : Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
}
),
new RaisedButton(
padding: const EdgeInsets.all(10.0),
child: const Text('Submit Answer'),
color: Color(0xFFE08284),
elevation: 0.0,
onPressed: () {
setState(() {
submit = 1;
});
},
),
],
),
submit == 1 ? new Container(
child: new Text("hello World"),
) : new Container()
],
)
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}