I have a page where a user makes a "post" (like twitter/ig) upon clicking submit that post is sent to the backend via a post request and then the page routes to the "Main Page" which is basically a scaffold with a persistent bottom navigation bar that creates the body based on which icon (index) in the navigation bar is pressed. By default the index is 0 (first icon) so the corresponding body is shown which also shows the newly created posts the user made by performing a get request to the server. But the problem is it doesn't show the posts unless I click hot reload. Similarly if I navigate to a different page by clicking one of the icons and come back to the first page the posts are gone till I press hot reload again. How can I ensure the posts are loaded each time the body/page is created?
Cody for page displaying the posts:
class PostPage extends StatefulWidget {
#override
_PostPageState createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
ApiClient _client = ApiClient();
String session;
int userID;
var refreshKey = GlobalKey<RefreshIndicatorState>();
Future GetInfo() async{
session = await getSession("session");
print("from get info "+ session);
userID = await getUserID("userID");
print("from get info "+ userID.toString());
}
#override
void initState() {
// TODO: implement initState
super.initState();
GetInfo();
print("called");
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Posts", style: Style.AppBarStyle),
bottom: TabBar(
tabs: [
Tab(
text: "Text1",
),
Tab(
text: "Text2",
),
],
),
),
body: TabBarView(
children: [
Posts(_client.getPostsOne(userID, session)),
Posts(_client.getPostsTwo(userID, session)),
],
),
),
);
}
}
Code for the future builder Posts:
Widget Posts(Future<List<Post>> future) {
return FutureBuilder<List<Post>>(
future: future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF585B8D), Color(0xFF252642)])),
child: CustomScrollView(
scrollDirection: Axis.vertical,
shrinkWrap: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => PostCard(
snapshot.data[index], false, true, false),
childCount: snapshot.data.length,
),
),
)
],
));
}
if (snapshot.data == null) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF585B8D), Color(0xFF252642)])),
child: Container());
}
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
});
}
EDIT It seemed like the GET request was being made before the userID/session key was loaded from shared preferences, adding setState({}) fixed this because it caused the widget to be repainted with the now retrieved userID/session key. But two get requests were made instead of one, to prevent this I checked if the session was null before calling or else I displayed an empty container.
session !=null?Posts(_client.getPostsOne(userID, session)):Container()
You forgot to call setState after you get the data (to rebuild the widget):
Future GetInfo() async{
session = await getSession("session");
print("from get info "+ session);
userID = await getUserID("userID");
print("from get info "+ userID.toString());
setState(() {
});
}
Related
I'm new on flutter, I have a Homepage where I have a Drawer menu and body list content.
DRAWER MENU => On tap item list of drawer menu I'm loading a PAGE web URL and on tap BACK it returns to my homepage. So it works very well.
BODY LIST CONTENT => On tap item list it loads the page web URL well BUT when I won't return back to my homepage it returns a black screen :(
Homepage.dart
class HomePage extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _HomePage();
}
}
class _HomePage extends State<HomePage>{
#override
Widget build(BuildContext context) {
// TODO: implement build
var globalContext = context;
return Scaffold(
appBar: AppBar(
title: Text(
'Benvenuto',
style: TextStyle(color: Colors.white)
),
backgroundColor: Color(0xFF4035b1),
),
drawer: Drawer(
child: new Column(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: Text('VIA ALBERTO POLIO 54'),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF4268D3),
Color(0xFF584CD1)
],
begin: FractionalOffset(0.2, 0.0),
end: FractionalOffset(1.0, 0.6),
stops: [0.0, 0.6],
tileMode: TileMode.clamp
)
),
accountEmail: Text('ORARI: LUNEDI - VENERDI 9:30 / 19:30'),
currentAccountPicture: new CircleAvatar(
radius: 50.0,
backgroundColor: const Color(0xFF778899),
backgroundImage: AssetImage("assets/img/icon_logo.jpg"),
)
),
// This list work well!
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
],
),
),
// The menu on my body load well the page web url but doesn't return back to my homepage.
body: new Column(
children: <Widget>[
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
])
);
}
}
Page.dart
class Page extends StatelessWidget{
final String titleText;
final String urlSource;
Page(this.titleText, this.urlSource);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new WebviewScaffold(
url: urlSource,
appBar: new AppBar(
title: Text(titleText),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
);
}
}
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: HomePage()
);
}
}
Thank you for your help guys!
You shouldn't be using Navigator.pop() just before Navigator.push().
If replacing the current page with a new one is what you want, you can use Navigator.of(context).pushReplacement().
If you only want to navigate to a new route delete the pop method and only use push
The real problem here is that when you're using Navigator.pop() you're removing it from the "pages stack". When you're using Navigator.pop() at the Drawer(), the ".pop" function removes the Drawer and keeps the main page.
But at the time you use it with the ListTile(), which is part of the "main body" of the page, you just remove it.
Whatever collapses the main page when pressed, such a Drawer, Dialog or even a Keyboard, will be removed using Navigator.pop(), any other thing that is at the page which implements the "Navigator.pop()" will remove the page instead.
Navigator.of(context).pop();
this one is popping the screen in the flutter.
you can refer this doc as well https://api.flutter.dev/flutter/widgets/Navigator/pop.html
your home page doesn't have any stack behind it so when you have written Navigator.of(context).pop(); then it will pop the home page where there is not anything and it always shows the blank screen.
when you have tried Navigator.of(context).pop(); in the drawer then it has home page as a stack in the flutter which is the home page in your case and it will pop to the home page and show the blank page.
So in my app, I have a screen with a tabBar. When the screen loads, it sorts out the items and moves them into different tabs. But when I run this, The Items keep duplicating, and I am shown an error in the debug console that says I called setState() after dispose()
Here's the code for the screen:
import 'package:flutter/material.dart';
import './uiComponents/customWidgets.dart';
import './ticketsComponents/ticketsList.dart';
import './tabs.dart';
class Tickets extends StatefulWidget {
Tickets({ this.tickets, this.user });
final List tickets;
final Map user;
#override
_TicketsState createState() => new _TicketsState();
}
class _TicketsState extends State<Tickets> with SingleTickerProviderStateMixin {
TabController controller; // Tab controller for the screen
List _tickets;
// Variables to Store the sorted Tickets
List _availableTickets = [];
List _usedTickets = [];
List _expiredTickets = [];
#override
void initState(){
controller = new TabController(
vsync: this,
length: 4,
initialIndex: 1
);
WidgetsBinding.instance.addPersistentFrameCallback((_) async {
// Get the tickets and sort them
_tickets = widget.tickets;
if(_tickets != null){
_sortTickets();
}
});
super.initState();
}
#override
void dispose(){
controller.dispose();
super.dispose();
}
// DELETE A TICKET (FROM ID)
void deleteTicket(int id){
setState(() {
_tickets.removeWhere((item)=> item["id"] == id);
_availableTickets = [];
_usedTickets = [];
_expiredTickets = [];
_sortTickets();
});
}
// SORT THE TICKETS INTO AVAILABLE / UNUSED, USED AND EXPIRED
void _sortTickets(){
for (int i = 0; i < _tickets.length; i++){
Map ticket = _tickets[i];
if(ticket["isUsed"]){
setState(() {
_usedTickets.add(ticket);
});
}
else if(ticket["expired"]){
setState(() {
_expiredTickets.add(ticket);
});
}
else{
setState(() {
_availableTickets.add(ticket);
});
}
}
}
// NAVIGATE TO MAIN TAB AND CLEAR PREVIOUS ROUTES
void _navProfile(){
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => Tabs(
user: widget.user
)
),
(route)=> false
);
}
// TabBar for Filtering Tickets
TabBar _buildTabBar(){
return new TabBar(
controller: controller,
indicatorWeight:2.2,
labelStyle: TextStyle(
fontSize:14.0,
fontWeight: FontWeight.bold
),
unselectedLabelStyle: TextStyle(
fontWeight:FontWeight.normal
),
labelColor: blue,
indicatorColor: blue,
unselectedLabelColor: Colors.black,
tabs: [
Tab(text: "All"),
Tab(text: "Available"),
Tab(text: "Used"),
Tab(text: "Expired")
],
);
}
// THE AppBar with the sub menu under it
AppBar _buildAppBar(){
Function onBackButtonPressed = _navProfile;
return new AppBar(
title: customTextBold("My Tickets"),
iconTheme: new IconThemeData(
color: blue
),
leading: GestureDetector(
child: Icon(Icons.arrow_back, color: blue),
onTap: onBackButtonPressed,
),
elevation: 1.0,
backgroundColor: Colors.white,
bottom: _buildTabBar()
);
}
// BUILD MAIN SCREEN
Container _buildTicketsPage(){
return new Container(
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: backgroundBlue
),
child: new TabBarView(
controller: controller,
children: [
TicketsList(
tickets: _tickets,
deleteTicket: deleteTicket,
),
TicketsList(
tickets: _availableTickets,
deleteTicket: deleteTicket,
),
TicketsList(
tickets: _usedTickets,
deleteTicket: deleteTicket,
),
TicketsList(
tickets: _expiredTickets,
deleteTicket: deleteTicket,
)
],
)
);
}
// UI
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: _buildAppBar(),
body: (_tickets == null)
? buildLoadingScreen("Fetching Tickets")
: _buildTicketsPage()
);
}
}
Running this will render the correct screen, but the ticket items will start duplicating, and this error is displayed on the debug console:
.
E/flutter (31673): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: setState() called after dispose(): _TicketsState#2cafe(lifecycle state: defunct, not mounted, ticker inactive)
E/flutter (31673): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the t
Please how do I fix this ?
add before each setState a condition
if(!mounted) return;
for example:
if(!mounted) return;
setState(() {
_expiredTickets.add(ticket);
});
I need to refresh my UI when data changes. I have a ListView to display Cards that contain my events, and these events are sorted with a datepicker. When I change the date with the datepicker I need to reload the page to display the correct pages.
I try to pass the datepicker data as a parameter of the ListView to sort the events in the ListView, I also tried to sort the data before ListView is built with a parameter containing the list of sorted data.
Widget of my HomePage class :
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: Image.asset('assets/logo2.PNG', fit: BoxFit.contain),
title: Text(widget.title,style: TextStyle(fontFamily: 'IndieFlower',fontSize: 30,fontWeight: FontWeight.bold),),
actions: <Widget>[ // Add 3 lines from here...
new IconButton(icon: const Icon(Icons.account_circle, color: Color(0xFFf50057)), onPressed: _pushSaved, iconSize: 35,),
], // ... to here.
centerTitle: true,
backgroundColor: new Color(0xFF263238),
),
body: FutureBuilder<List<Event>>(
future: fetchPosts(http.Client()),
builder: (context, snapshot) {
//print(convertIntoMap(snapshot.data));
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListViewEvents(posts: sortEvents(snapshot.data), pickerDate: '${dobKey.currentState.dobDate} ' +dobKey.currentState.dobStrMonth +' ${dobKey.currentState.dobYear}')
: Center(child: CircularProgressIndicator(backgroundColor: Color(0xFFf50057),));
},
),
bottomNavigationBar : BottomAppBar(
child: Container(height: 100.0,
alignment: Alignment.topCenter,
child:
DatePicker(
key: dobKey,
setDate: _setDateOfBirth,
customItemColor: Color(0xFFf50057),
customGradient:
LinearGradient(begin: Alignment(-0.5, 2.8), colors: [
Color(0xFFf50057),
Color(0xFFffcece),
Color(0xFFf50057),
]),
),
),
),
);
}
}
This is my map:
List<Event> sortEvents(List<Event> data) {
List<Event> eventsSelected = new List<Event>();
for(var index = 0; index < data.length; index++){
if (data[index].date ==
//callback of datepicker
'${dobKey.currentState.dobYear}-${dobKey.currentState.month}-
${dobKey.currentState.dobDate}') {
eventsSelected.add(data[index]);
}
}
return eventsSelected;
}
And this is how I render my cards:
class ListViewEvents extends StatefulWidget {
ListViewEvents({Key key, this.posts, this.pickerDate}) : super(key: key);
final posts;
final pickerDate;
#override
_ListViewEventsState createState() => _ListViewEventsState();
}
class _ListViewEventsState extends State<ListViewEvents> with
SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
if(widget.posts.isEmpty) {
return Center(
child: Text(
'No events for this date'
),
);
}
return ListView.builder(
itemCount: widget.posts.length,
padding: const EdgeInsets.all(15.0),
itemBuilder: (context, index) {
return Center(
child: Text(
'Title : ${widget.posts[index].title}'
),
);
},
);
}
}
I actually have a system to display my events's Cards that works but it's not in real-time, I would like to refresh the UI when the data of the datepicker changes.
You need to call setState() when list data changes.
for(var index = 0; index < data.length; index++){
if (data[index].date ==
//callback of datepicker
'${dobKey.currentState.dobYear}-${dobKey.currentState.month}-
${dobKey.currentState.dobDate}') {
setState(() { eventsSelected.add(data[index]); } ); <--- add it here.
}
}
you can use setState() for this problem so setState() call when anything change in the screen
I am trying to create a UX that looks like WhatsApp Dashboard in Flutter. I created a Scaffold with an AppBar and put the TabBar in the bottomNavigationBar slot instead of the bottom slot of the AppBar. Each of the TabBarView children is a StreamBuilder that listens to particular stream. The problem is that whenever the stream emits a value the StreamBuilder rebuilds (checked via logging build function) but the UI doesn't update until I switch tabs and come back to the tab.
I have tried creating a stateful widget that hosts the StreamBuilder and instantiating that as a child of the TabBarView. I also tried adding a listener to the stream and calling setState there but it didn't work either.
I expect the page to update the UI whenever a chat message is received but it doesn't update until I switch tabs.
body: TabBarView(
controller: _tabController,
children: <Widget>[
ChatListView(),
...
class ChatListView extends StatefulWidget {
#override
_ChatListViewState createState() => _ChatListViewState();
}
class _ChatListViewState extends State<ChatListView>
with AutomaticKeepAliveClientMixin {
List<ListTile> itemList = <ListTile>[];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: chatsListBloc.chatList,
builder: (context, snapshot) {
print("rebuilt");
if (!snapshot.hasData) {
chatsListBloc.fetchChatList();
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'You have not started any chat yet. To start a chat, click on the Start Chat icon.',
textAlign: TextAlign.center,
),
),
);
} else {
List<ChatListItem> dataList = List<ChatListItem>.from(snapshot.data);
itemList.clear();
for (int i = 0; i < dataList.length; i++) {
itemList.add(ListTile(
onTap: () {
}
},
title: Text(dataList[i].displayName),
subtitle: dataList[i].lastMessage,
leading: CircleAvatar(
backgroundColor: Colors.blueGrey,
backgroundImage:MemoryImage(dataList[i].avatar),
child: Stack(
children: <Widget>[
Icon(
Icons.account_circle,
size: 40,
),
(dataList[i].type == ChatType.Incognito)
? Icon(Icons.lock,
color: Colors.blueGrey[700], size: 10)
: Container(),
],
),
),
trailing: StreamBuilder(
stream: Stream.periodic(Duration(seconds: 1),
(computationCount) => computationCount)
.asBroadcastStream(),
builder: (context, snapshot) => Text(timeLabel(
DateTime.fromMillisecondsSinceEpoch(
dataList[i].lastAccessed))),
)));
}
return ListView(
children: itemList,
);
}
}
});
}
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => false;
}
I'm new to Flutter,
I want to destruct cards created initially and construct them again as per data provided in API call.
Basically when I tap on button in UI, it should call APIs and based on data from API call, if it is different from the data I already have, I want to destruct cards and construct them again.
How I can achieve this?
The cards will auto update their content when you make the call again, it is like refreshing your data.
I have made a simple example with a single card that shows data from this JSON Where I am calling the API first time in initState and then repeating the call each time I press on the FAB.
I am adding the index variable just to show you the updates (updating my single card with the next item in the list)
Also it is worth noting that I am handling the null or empty values poorly for the sake of time.
Also forget about the UI overflow ¯_(ツ)_/¯
class CardListExample extends StatefulWidget {
#override
_CardListExampleState createState() => new _CardListExampleState();
}
class _CardListExampleState extends State<CardListExample> {
Map cardList = {};
int index = 0;
#override
void initState() {
_getRequests();
super.initState();
}
_getRequests() async {
String url = "https://jsonplaceholder.typicode.com/users";
var httpClinet = createHttpClient();
var response = await httpClinet.get(
url,
);
var data = JSON.decode(response.body);
//print (data);
setState(() {
this.cardList = data[index];
this.index++;
});
print(cardList);
print(cardList["name"]);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton:
new FloatingActionButton(onPressed: () => _getRequests()),
appBar: new AppBar(
title: new Text("Card List Example"),
),
body: this.cardList != {}
? new ListView(children: <Widget>[
new Card(
child: new Column(
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Text(
cardList["name"] ?? '',
style: Theme.of(context).textTheme.display1,
),
new Text(
this.cardList['email'] ?? '',
maxLines: 50,
),
],
),
new Text(cardList["website"] ?? '')
],
),
),
])
: new Center(child: new CircularProgressIndicator()),
);
}
}
Yes, Answer from Aziza works.
Though I used the code as below :
void main() =>
runApp(new MaterialApp(
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/about':
return new FromRightToLeft(
builder: (_) => new _aboutPage.About(),
settings: settings,
);
}
},
home : new HomePage(),
theme: new ThemeData(
fontFamily: 'Poppins',
primarySwatch: Colors.blue,
),
));
class HomePage extends StatefulWidget{
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage>{
List data;
Future<String> getData() async{
var response = await http.get(
Uri.encodeFull(<SOMEURL>),
headers: {
"Accept" : "application/json"
}
);
this.setState((){
data = JSON.decode(response.body);
});
return "Success";
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar : new AppBar(
title : new Text("ABC API"),
actions: <Widget>[
new IconButton( // action button
icon: new Icon(Icons.cached),
onPressed: () => getData(),
)],
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new Container(
height: 120.0,
child: new DrawerHeader(
padding: new EdgeInsets.all(0.0),
decoration: new BoxDecoration(
color: new Color(0xFFECEFF1),
),
child: new Center(
child: new FlutterLogo(
colors: Colors.blueGrey,
size: 54.0,
),
),
),
),
new ListTile(
leading: new Icon(Icons.chat),
title: new Text('Support'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/support');
}
),
new ListTile(
leading: new Icon(Icons.info),
title: new Text('About'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/about');
}
),
new Divider(),
new ListTile(
leading: new Icon(Icons.exit_to_app),
title: new Text('Sign Out'),
onTap: () {
Navigator.pop(context);
}
),
],
)
),
body: this.data != null ?
new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index){
return new Container(
padding: new EdgeInsets.fromLTRB(8.0,5.0,8.0,0.0),
child: new Card(
child: new Padding(
padding: new EdgeInsets.fromLTRB(10.0,12.0,8.0,0.0),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
enabled: data[index]['active'] == '1' ? true : false,
title: new Text(data[index]['header'],
style:Theme.of(context).textTheme.headline,
),
subtitle: new Text("\n" + data[index]['description']),
),
new ButtonTheme.bar(
child: new ButtonBar(
children: <Widget>[
new FlatButton(
child: new Text(data[index]['action1']),
onPressed: data[index]['active'] == '1' ? _launchURL :null,
),
],
),
),
],
),
),
),
);
},
)
:new Center(child: new CircularProgressIndicator()),
);
}
}
_launchURL() async {
const url = 'http://archive.org';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
class FromRightToLeft<T> extends MaterialPageRoute<T> {
FromRightToLeft({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
return new SlideTransition(
child: new Container(
decoration: new BoxDecoration(
boxShadow: [
new BoxShadow(
color: Colors.black26,
blurRadius: 25.0,
)
]
),
child: child,
),
position: new Tween(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
)
.animate(
new CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
)
),
);
}
#override Duration get transitionDuration => const Duration(milliseconds: 400);
}
The above code includes Navigation drawer, page navigation animation and also answer to the above question.