As I am a new dev in Flutter it’s very confusing me to when should I call setState() ?, If I call this entire application is reloading (redrawing view) in build(). I want to update one TextView widget value in tree widgets structure
Here is example. On click on fab you recreate only _MyTextWidget
StreamController<int> _controller = StreamController<int>.broadcast();
int _seconds = 1;
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.cyan.withOpacity(0.3),
width: 300.0,
height: 200.0,
child: _MyTextWidget(_controller.stream)),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.add(_seconds++);
},
child: Icon(Icons.add),
),
);
}
...
class _MyTextWidget extends StatefulWidget {
_MyTextWidget(this.stream);
final Stream<int> stream;
#override
State<StatefulWidget> createState() => _MyTextWidgetState();
}
class _MyTextWidgetState extends State<_MyTextWidget> {
int secondsToDisplay = 0;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: widget.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return snapshot.hasData ? Text(snapshot.data.toString()) : Text('nodata');
});
}
}
To make it simple, SetState() {} invalidates the widget in which it is called and forces the widget to rebuild itself by calling build(). That means that every child widgets are being rebuilt.
There are other methods you can use to pass data to a widget down a tree and make it rebuilt itself (and all its chidlren) than using SetState () {}. Those are really helpfull, especially if the widget you want to rebuilt is far away from yours in the widget tree.
One of them is the example provided by #andrey-turkovsky that uses a combination of StreamBuilder and a Stream. The StreamBuidler is a widget that rebuilt itself when there is an interaction in a Stream. Based on that, the idea is to wrap your TextView in a StreamBuilder, and use the stream to sent the data you want your TextView to display.
Related
I'm trying to display a comment stream from Reddit API. I"m using Streambuilder to stream contents as it arrives and displays it as list view thing is I can only view present stream content and this will disappear as new stream contents appear replacing the old ones. If I don't mention item count inside listview.builder prints contents infinitely still new stream appears.
is there a way to display contents along with previous contents in a scrollable fashion and automatically scroll down as a new stream message appears??
Assuming that the comment stream returns individual (and preferably unique) comments one at a time rather than as a list, what you need to do is store the incoming comments in a state object such as a list. When a new comment comes through the stream, you add it to the list and then trigger a widget rebuild.
What you are doing right now is replacing state with each new stream element rather than accumulating them. Using the code you provided, I have edited it to behave as an accumulator instead. Notice the List<Comment> comments = <Comment>[] object added to state. I have also removed the StreamBuilder since that isn't helpful for this use case.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:draw/draw.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: RedditFlutter(),
debugShowCheckedModeBanner: false,
);
}
}
class RedditFlutter extends StatefulWidget {
RedditFlutter({Key key}) : super(key: key);
#override
_RedditFlutterState createState() => _RedditFlutterState();
}
class _RedditFlutterState extends State<RedditFlutter> {
var comments;
ScrollController _scrollController =
new ScrollController(initialScrollOffset: 50.0);
List<Comment> comments = <Comment>[];
StreamSubscription<Comment>? sub;
var msg = '';
Future<void> redditmain() async {
// Create the `Reddit` instance and authenticated
Reddit reddit = await Reddit.createScriptInstance(
clientId: 'clientid',
clientSecret: 'clientsecret',
userAgent: 'useragent',
username: 'username',
password: 'password', // Fake
);
// Listen to comment stream and accumulate new comments into comments list
sub = reddit.subreddit('cricket').stream.comments().listen((comment) {
if (comment != null) {
// Rebuild from state when a new comment is accumulated
setState(() {
comments.add(comment);
})
}
});
}
#override
void initState() {
// TODO: implement initState
redditmain();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Reddit"),
centerTitle: true,
),
body: Center(
child: Container(
child: ListView.builder(
controller: _scrollController,
itemCount: comments.length,
itemBuilder: (context, index) {
final Comment comment = comments[index];
return Card(
child: ListTile(
leading: Image.asset('assets/criclogo.png'),
title: Text(comment.body),
trailing: Icon(Icons.more_vert),
),
);
},
),
);
),
);
}
#override
void dispose() {
sub?.cancel();
super.dispose();
}
}
Note that I have not tested this code so there may be (trivial) bugs. Still, conceptually, it should be fine.
I am trying to create a flexible navigation system, that uses one page to display different things pulled from a database.
For example
If I would click the button Cars it would navigate to the fixed page (Lets call it display page) And it would pull the title and description from the database and show accordingly. So by clicking Cars, it would send the String Cars with it, so i could use it to find the desired thing from the database.
My question is, how can I send this info to the next page?
I want to use
ElevatedButton(
child: Text("Cars"),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => HabitPage()));
},
And send the desired item with HabitPage('cars(for example)') (I have studied C and that's how you would send variables, not sure it will work in dart.)
This is my HabitPage
class HabitPage extends StatefulWidget {
#override
_HabitPageState createState() => _HabitPageState();
}
class _HabitPageState extends State<HabitPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar
(
title: new Text("HabitPage")
),
body: new Center(
child: new Text("HabitPage"),
),
);
}
}
ElevatedButton(
child: Text("Cars"),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => HabitPage(info: 'Cars'))); // Passing the info as argument
},
class HabitPage extends StatefulWidget {
HabitPage({this.info}) : super(key: key);
final String info; // Variable to receive the info as argument
#override
_HabitPageState createState() => _HabitPageState();
}
class _HabitPageState extends State<HabitPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar
(
title: new Text("HabitPage ${widget.info}")// Showing the info passed as argument
),
body: new Center(
child: new Text("HabitPage"),
),
);
}
}
Make this changes in your code.
In the future, you will probably use Named Routes, so you will need this tutorial.
https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments#1-define-the-arguments-you-need-to-pass
But for now, this is the solution
The class in question is invoked from another page with the line
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) =>
ProPage(iD: bestRatedPros[index]["ID"])));
},
Where bestRatedPros is a list of maps with the variable iD for the following class -
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ProPage extends StatefulWidget {
ProPage({Key key, this.iD}) : super(key: key);
final iD;
#override
_ProPageState createState() => _ProPageState(iD);
}
class _ProPageState extends State<ProPage> {
int iD;
_ProPageState(this.iD);
#override
void initState() {
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber,
extendBodyBehindAppBar: true,
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.white, //change your color here
),
elevation: 0,
backgroundColor: Colors.amber
),
body:
Text("EWFWEFEWEWFWEF",style: TextStyle(color: Colors.black))
);
}
}
The getDataFromBackend function and
all the variables associated with it was meant to be within the body. But Nothing shows up in the body no matter what it is. Even a simple Text widget doesn't. I'm only trying to pass the variable iD from one page to the other without complicating things. The Run log doesn't show any Errors or warnings.
Arun,
See below where your Text is:
Reason for that is that you specified:
extendBodyBehindAppBar: true,
on your Scaffold, so body is expanded and top part of it is hidden behind AppBar
Problem:
I have a parent widget SnippetTestEditor and a stateful child widget NoteTab. If a button in the parent widget is pressed, the child widget should get updated.
Both classes have a bool _editMode. I pass the bool from the parent widget to the child widget via the constructor. From my understandig, I need to call setState() in the parent widget and change the bool within setState(). This change should automatically be reflected in the child widget. But it's not....
So how can I get the child widget to change?
Code:
class _SnippetTestEditorState extends State<SnippetTestEditor> with SingleTickerProviderStateMixin {
bool _editMode = true;
...
#override
void initState() {
super.initState();
_tabs = List();
_tabNames = List();
List<CodeSnippet> codeSnippets = this.widget._note.codeSnippets;
for(CodeSnippet snippet in codeSnippets){
_tabs.add(CodeTab(_editMode);
...
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: TabBarView(
controller: _tabController,
children: _tabs
),
...
actions: <Widget>[
IconButton(
icon: Icon(Icons.check),
onPressed: (){
setState(() {
_editMode = false;
});
},
)
...
class CodeTab extends StatefulWidget{
bool _editMode;
CodeTab(this._editMode);
#override
State<StatefulWidget> createState() => CodeTabState();
}
class CodeTabState extends State<CodeTab> {
#override
Widget build(BuildContext context) {
return this.widget._editMode ? ...
This happens because _editMode value is passed to CodeTab only once, inside initState(). So, even though the build method is called multiple times, the CodeTab instances in _tabs do not get updated.
you should move the code to create tabs in a method inside the state class:
getTabs(){
List<CodeSnippet> codeSnippets = widget._note.codeSnippets;
for(CodeSnippet snippet in codeSnippets){
_tabs.add(CodeTab(_editMode);
return _tabs;
}
and use getTabs() in build:
body: TabBarView(
controller: _tabController,
children: getTabs(),
),
If you have any doubts in this solution, Let me know in the comments.
Given 2 routes, e.g. parent and a child and a Hero(..) widget with the same tag.
When the user is on the "parent" screen and opens a "child" - the Hero widget is animated. When it goes back (via Navigator.pop) it's also animated.
I'm looking for a way to disable that animation when going back (from child to parent via Navigator.pop).
Is there a kind of handler which will be called on a widget before it's going to be "animated away" ? Then I probably could change Hero tag and problem solved.
Or, when creating a "builder" for a route in parent widget, I could probably remember a reference to a target widget and before calling Navigator.pop notify it about "you are gonna be animated out". That would also require making that widget stateful (I haven't found a way to force rebuild a stateless widget).
Is there an easier way of implementing this?
While there currently isn’t a built-in way to disable Hero animations in any particular direction, though CLucera’s use of FadeTransition with HeroFlightDirection is one creative way, the most direct approach is to break the tag association between the two Hero’s:
When you go from the 2nd Hero back to the 1st Hero, just temporarily change the 1st Hero’s tag to something else, then the Hero won’t animate back. A simplified example:
class _MyHomePageState extends State<MyHomePage> {
String tag1, tag2;
String sharedTag = 'test';
String breakTag = 'notTest';
#override
void initState() {
super.initState();
tag1 = sharedTag;
tag2 = sharedTag;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Hero(
tag: tag1,
child: RaisedButton(
child: Text("hi"),
onPressed: () {
// restore the tag
if (tag1 != sharedTag) {
setState(() {
tag1 = sharedTag;
});
}
// second route
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
alignment: Alignment.topLeft,
child: Hero(
tag: tag2,
child: RaisedButton(
child: Text('hello'),
onPressed: () {
// change the tag to disable the reverse anim
setState(() {
tag1 = breakTag;
});
Navigator.of(context).pop();
},
),
)
),
);
}
)
);
},
)
),
),
);
}
}
But if you want to directly modify the animation, then playing around inside the flightShuttleBuilder is the way to do it like CLucera did. You can also check out medium/mastering-hero-animations-in-flutter to further explore that area.
The only approach that I can come up at the moment is to "Animate" the popping Hero in a way that seems not animated, let's check this code:
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Hero(
flightShuttleBuilder: (context, anim, direction, fromContext, toContext) {
final Hero toHero = toContext.widget;
if (direction == HeroFlightDirection.pop) {
return FadeTransition(
opacity: AlwaysStoppedAnimation(0),
child: toHero.child,
);
} else {
return toHero.child;
}
},
child: FlatButton(
child: Text("prev 1"),
onPressed: () {
Navigator.of(context).pop();
},
),
tag: "test",
));
}
}
in your SecondRoute (the one that should pop) you have to supply a flightShuttleBuilder parameter to your Hero then you can check the direction and if it is popping, just hide the Widget with an AlwaysStoppedAnimation fade transition
the result is something like this:
I hope that this is something like the expected result, of course, you can completely change the transition inside the flightShuttleBuilder to change the effect! it's up to you :)