Flutter skip children in ListView or PageView - dart

Let's say I have 4 children in my PageView as
PageView(
children: <Widget>[
Page1(),
Page2(),
Page3(), // how to skip this when a condition is false
Page4(),
],
)
I want to show Page3 when a bool value is true, how do I do that, I thought of putting a null but I am not allowed to do that.

One way to do is with - With sync* functions.
Page2 will be displayed if the condition is true.
bool condition = true;
PageView(
children: List.unmodifiable(() sync* {
yield Center(child: Text('Page1')); //Page 1
if (condition) {
yield Center(child: Text('Page2')); // Page2(conditional)
}
// yield* children;
yield Center(child: Text('Page3')); //Page3
}()),
),

Beginning with Dart 2.2.2, you can use normal if condition.
PageView(
children: <Widget>[
Page1(),
Page2(),
if (_condition) Page3(), // visible only when condition is true
Page4(),
],
)

Seems like this is a better way to make the things happen.
List<Widget> list = [];
if (condition) {
list = [
Page1(),
Page2(),
Page3(),
Page4(),
];
} else {
list = [
Page1(),
Page2(),
Page4(),
];
}
return PageView(children: list);

Related

Flutter webview navigationDelegate called twice

I am trying to download files which contains a certain url pattern with Flutter web view. This works but in this case the browser is opened twice, as the navigationDelegate is called twice. NavigationRequest Object is same except isForMainFrame property. It is false for first time, and true for second time.
CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(middle: Text(_appTitle)),
child: Container(
child: SafeArea(
child: IndexedStack(
index: _stackToView,
children: <Widget>[
WebView(
key: _key,
javascriptMode: JavascriptMode.unrestricted,
initialUrl: this._connectionString,
onPageStarted: (value) => setState(() {
if (shouldChangeStack) {
_stackToView = 1;
} else {
_stackToView = 0;
}
}),
onPageFinished: (value) => setState(() {
_stackToView = 0;
}),
navigationDelegate: (NavigationRequest request) async {
print(request.url);
if (request.url.contains("download")) {
setState(() {
shouldChangeStack = false;
});
if (await canLaunch(request.url)) {
await launch(request.url);
}
return NavigationDecision.prevent;
} else {
setState(() {
shouldChangeStack = true;
});
return NavigationDecision.navigate;
}
},
),
Container(
child: Center(
child: CircularProgressIndicator(),
),
)
],
),
top: true,
),
),
);
The reason why the delegate method is called twice was because setState() is called. This causes the entire Widget build() to be rebuilt. As previously mentioned in the comments, a workaround for this issue is to set a checker before launching the page and define if the page needs to be opened.

Stream is not re-rendering when switching tabs on flutter

I have a stream builder that shows a list of "posts" from a server. I have used the BLoC architecture to accomplish this. But for some reason when I switch tabs and back the posts disappear how can I keep the posts from disappearing or have them re-render? Below is small part of my code I think is relevant I can add more if needed:
Tab UI (not all the code, file containing BLoC is imported at top):
#override
void initState() {
bloc.fetchMyPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Posts", style: Style.appBarStyle),
bottom: TabBar(
tabs: [
Tab(
text: "My Posts",
),
Tab(
text: "My Other Posts",
),
],
),
),
body: TabBarView(
children: [
Posts(stream: bloc.myPosts), //Stream builder with SliverChildBuilderDelegate
Posts(stream:bloc.myOtherPosts),//Stream builder with SliverChildBuilderDelegate
],
),
),
);
}
Stream Builder (Posts):
Widget Posts({Stream stream, //Other variables}) {
return StreamBuilder(
stream:stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch(snapshot.connectionState) {
case ConnectionState.none:
return Row(
children: <Widget>[
Flexible(
child: Text("Please check if you are connected to the internet"),
),
],
);
break;
case ConnectionState.waiting:
if (snapshot.data == null){
return Container(
color: Color(0xFFF4F4FF),
child: Container(child:Center(child:Text(variable?"Text one":"Text two"))));
} else return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
Center(
child: Text("Loading"),
),
],
);
break;
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.hasData) {
return Container(
color:Colors.white,
child: CustomScrollView(
scrollDirection: Axis.vertical,
shrinkWrap: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => PostCard(post:snapshot.data[index],//variables),
childCount: snapshot.data.length,
),
),
)
],
));
}
if (snapshot.data == null){
return Container(
color: Color(0xFFF4F4FF),
child: Container(child:Center(child:Text(variable?"Text one":"Text two"))));
}
}
});
}
BLoC:
class Bloc{
ApiClient _client = ApiClient();
final _myPosts = BehaviourSubject<List<Post>>();
final _myOtherPosts = BehaviourSubject<List<Post>>();
Stream<List<Post>> get myPosts => _myPosts.stream;
Stream<List<Post>> get myOtherPosts => _myOtherPosts.stream;
fetchMyPosts() async {
List<Post> posts = await _client.getMyPosts();
_myPosts.sink.add(posts);
}
fetchMyOtherPosts() async {
List<Post> posts = await _client.getMyOtherPosts();
_myOtherPosts.sink.add(posts);
}
dispose(){
_myPosts.close();
_myOtherPosts.close();
}
}
final bloc = Bloc();
Main Screen:
class MainScreen extends StatefulWidget {
UserBloc userBloc;
MainScreen({this.userBloc});
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
Widget getPage(int index) {
if (index == 0) {
return PostPage(myHandle: widget.userBloc.userValue);
}
if (index == 1) {
return PageOne();
}
if (index == 3) {
return PageTwo();
}
if (index == 4) {
return PageThree(userBloc: widget.userBloc);
}
return PostPage(userBloc: widget.userBloc);
}
Widget customNav() {
return Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(Icons.library_books),
onPressed: () => setState(() {
_currentIndex = 0;
})),
// MORE ICONS but similar code
],
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(children: <Widget>[
getPage(_currentIndex),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: customNav(),
),
]));
}
}
Take a look at this code i put some comments. I can do this using streambuilder and bloc pattern simulating an async data fetching with future delayed. This widget is working but you will need adapt to your needs.
class TabWidget extends StatefulWidget {
#override
_TabWidgetState createState() => _TabWidgetState();
}
class _TabWidgetState extends State<TabWidget> with SingleTickerProviderStateMixin {
Bloc _bloc;
#override
void initState() {
super.initState();
_bloc = Bloc(); // can be your bloc.fetchData();
}
#override
void dispose() {
_bloc?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//i really recomment using stream builder to create all layout
// if length property is dynamic
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Tab screen"),
bottom: TabBar(
tabs: [
Tab( text: "My Posts" ),
Tab( text: "Other" ),
],
),
),
body: StreamBuilder<List<Widget>>(
stream: _bloc.getTabData,
builder: (context, asyncSnapshot){
switch(asyncSnapshot.connectionState){
case ConnectionState.none:
return Row(
children: <Widget>[
Flexible(
child: Text("handle none state here, this is because i am simulate a async event"),
),
],
);
break;
case ConnectionState.waiting:
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
Center(
child: Text("Loading data..."),
),
],
);
break;
case ConnectionState.active:
case ConnectionState.done:
//assuming that snapshot has valid data...
return TabBarView(
children:[
asyncSnapshot.data[0],
asyncSnapshot.data[1],
],
);
}
}
),
),
);
}
}
class Bloc{
// post items
// just to simulate data
List<Widget> _tabList1 = List.generate(10, (index){ return Text("TAB 1 Item $index");} );
List<Widget> _tabList2 = List.generate(10, (index){ return Text("TAB 2 Item $index");} );
//tab's data stream
PublishSubject< List<Widget>> _tabData = PublishSubject();
Observable<List<Widget>> get getTabData => _tabData.stream;
Bloc() {
Future.delayed(Duration(seconds: 5), () {
List<Widget> tabDataWidgets = List();
// adding tab's data
tabDataWidgets.add( ListView(
children: _tabList1,
) );
tabDataWidgets.add( ListView(
children: _tabList2,
) );
_addingToSink( tabDataWidgets );
});
}
void _addingToSink( final List<Widget> list) => _tabData.sink.add( list );
dispose(){ _tabData?.close(); }
}
I changed PublishSubject to BehaviourSubject and it seemed to work. I used it in conjunction with Marcos Boaventura's answer as well. Although I used two stream builders.
PublishSubject: Starts empty and only emits new elements.
BehaviorSubject: It needs an initial value and replays it or the latest element to new subscribers.
I have fixed it another way like as I have take two Streem variable Example
final Stream<QuerySnapshot> showpost = FirebaseFirestore.instance
.collection("post")
.snapshots();
final Stream<QuerySnapshot> showpost2 = FirebaseFirestore.instance
.collection("post")
.snapshots();
And two-stream i have use two-variable ;

Change Variable values in Flutter

I'm trying to change some variables in different methos in Flutter, but the value isn't changed.
An example is something like:
enum UserPlaceStatusType { NONE, GOING, THERE, OUT, CANCELLED }
class PlaceCardState extends State<PlaceCard> {
UserPlaceStatusType _isOtherPlaceActive = UserPlaceStatusType.NONE;
Widget build(BuildContext context) {
return Card(
child: Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this._getBody(),
),
bottomNavigationBar: this._getBottomNavigationBar()));
}
List<Widget> _getBody() {
return [
Expanded(child: Text('test'), flex: 3),
Expanded(child: Text('test'), flex: 6),
Expanded(child: this._getActionsMenu(), flex: 1)
];
}
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
//It opens a simple dialog
child: this._getCurrentUserPlaceStatus());
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
return new GraphqlProvider(
client: new ValueNotifier(
Client(endPoint: 'GraphQLUrl', cache: new InMemoryCache()),
),
child: new Query(
'The GraphQL Query',
variables: {},
builder: ({
bool loading,
var data,
var error,
}) {
if (data != null && data['getCurrentUserPlaceStatus'] != null) {
this._isOtherPlaceActive = UserPlaceStatusType.THERE;
Navigator.pop(context, DialogActions.cancel);
return Container();
} else {
this._isOtherPlaceActive = UserPlaceStatusType.GOING;
Navigator.pop(context, DialogActions.cancel);
return Container();
}
},
));
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) async {
//here there is a validation but the variable value is the initial one, I mean NONE
if (this._isOtherPlaceActive == UserPlaceStatusType.GOING) {
//Cod to do
return;
}
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
this._isOtherPlaceActive = UserPlaceStatusType.NONE;
Navigator.pop(context);
}
});
}
}
I changed the variable value through some methods, but when I need to apply the validation, that's the initial value, it isn't changed, and I could not apply SetState method cuz it breaks the modal and throws an exception.
I will appreciate any feedback.
The method setState() can't be called inside a widget directly. I'm curious with your use of GrapQLProvider since it returns an empty Container() widget just to check the status of the data.
While I'm unfamiliar with the use of GraphQL, if the client that you're using inherits either a Stream or Future, it can be used to listen when the query is done.
Here's some snippets as demo. Let _testFuture() as the sample for a Future callback.
Future _testFuture() async{
return null;
}
Future can be listened to inside a Widget. When the request finishes, we have the opportunity to call setState().
_testFuture().then((value) {
// Check for values here
setState(() {
// Update values
});
});
Or if the request is set in a Stream, it's also possible to listen for Stream changes inside a Widget.
_streamController.add(_testFuture());
_streamController.stream.listen((event) {
// Check for values here
setState(() {
// Update values
});
});
This may not be the exact answer that you're looking for, but I hope this can guide you for a solution to your approach. I also found a GraphQL sample that uses ObservableQuery as a Stream that you can try.
Your code is very complex and should be refactored. Please notice how dialogs must be called.
enum DialogResult {ok, cancel}
caller_widget.dart
FlatButton(
child: Text('Open dialog'),
onPressed: () async {
// Call dialog and wait for result (async call)
final dialogResult = await showDialog<DialogResult>(
context: context,
builder: (context) => DialogWidget(),
);
if (dialogResult == DialogResult.ok) {
// do something
}
},
),
dialog_widget.dart
...
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.pop(context, DialogResult.ok), // DialogResult.ok returns
),
FlatButton(
child: Text('Cancel'),
OnPressed: () => Navigator.pop(context, DialogResult.cancel), // DialogResult.cancel returns
),
So you can return required value from dialog and set it to required variable.
P.S. Try to avoid use of old fashion then process of futures and use async/await.

Unable to remove added list element

I've added a list element by using the following method:
listToAddTo.add(italianFood());
However, when I try removing the same element by using the method:
listToAddTo.remove(italianFood());
it does not work.
I've tried using the removeWhere method with the parameters item == italianFood(), the retainWhere method with the item != italianFood() parameters, and the removeAt method with the listToAddTo.indexOf(italianFood()) parameters, however, none of those seem to work.
When I try printing the list, I get the following result:`
ListView(
scrollDirection: vertical,
primary: using primary controller,
scrollPhysics: AlwaysScrollableScrollPhysics,
shrinkWrap: shrink-wrapping
)
Above methods with this result also seem to have no effect.
Necessary code is as follows:
List listToAddTo = [];
ListView italianFood() {
return ListView(
shrinkWrap: true,
children: <Widget>[
listEntry(
'Fast Food Nana',
'Mon - Sun 06:00 - 04:00',
Container(
child: IconButton(
icon: Icon(
Icons.motorcycle,
size: 30.0,
),
),
),
IconButton(
icon: Icon(
Icons.navigation,
size: 30.0,
),
),
),
],
);
Container filterItem(String label, value, onChanged) {
return Container(
decoration: BoxDecoration(color: Colors.white),
child: CheckboxListTile(
value: value,
title: Text(label),
onChanged: onChanged,
),
);
bool isTrue = false;
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
margin: const EdgeInsets.only(top: 45.0),
child: ListView.builder(
itemCount: listToAddTo.length,
itemBuilder: (BuildContext context, int Index) {
return (listToAddTo[Index]);
},
),
),
ExpansionTile(
title: Text('Filters'),
children: <Widget>[
filterItem(
'Italian',
isTrue,
(bool value) {
setState(() {
if (isTrue == false) {
listToAddTo.add(italianFood());
isTrue = !isTrue;
} else {
listToAddTo.removeAt(listToAddTo.indexOf(italianFood()));
isTrue = !isTrue;
}
});
print(listToAddTo);
print(italianFood());
},
),
],
),
],
);
You can't remove it because are different objects.
Every time you use italianFood() you create a new instance of the class.
Create a gloval variable :
ListView _myItalianFood;
Instantiate:
in your add method:
_myItalianFood = italianFood();
listToAddTo.add(_myItalianFood);
Remove:
listToAddTo.remove(_myItalianFood);
The problem is that italianFood() returns a new ListView instance on each call. So when you call listToAddTo.add(italianFood()) you are creating a new ListView instance (let's call it LV1) and you end up with listToAddTo being [LV1].
When you call listToAddTo.remove(italianFood()) you are creating a new ListView instance (let's call it LV2) and asking listToAddTo to remove anything that is equal to LV2, so this ends up doing nothing since LV2 is not in listToAddTo.
You could fix this by making italianFood a field.
final italianFood = ListView(shrinkWrap: true, ...);

State not updating

I'm trying to update a widget when I got some data from my database. The widget that I'm trying to change is defined as a class variable:
Widget openFriendRequestNotificationWidget = new Container();
I'm using an empty container because I really don't need to render anything at the beginning and leaving it at null is no option.
I've got two functions, one to create my page and the other one the update my openFriendRequestNotificationWidget:
Widget createFriendsPage() {
if (currentUser.friends == null) {
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
openFriendRequestNotificationWidget,
new Material(
child: new InkWell(
child: new Center(
child: new Text("Woops, looks like you have no friends yet.\nTap here to find some!", textAlign: TextAlign.center,),
),
onTap: () => createFriendsDialog(),
)
)
],
);
}
return new Column(
children: <Widget>[
openFriendRequestNotificationWidget,
new Text("ok")
],
);
}
void createReceivedFriendRequestsNotification() {
FirebaseDatabase.instance.reference().child("friend_requests").child(currentUser.uid).once().then((DataSnapshot snap) {
Map<String, Map<String, String>> response = snap.value;
if (response != null) {
this.setState(() {
print("Changing widget");
openFriendRequestNotificationWidget = new Container(
child: new Text("You've got ${response.length.toString()} new friend requests!"),
color: Colors.black,
);
});
}
});
}
The variable is updating in createReceivedFriendRequestsNotification but it is not re-rendering.
Could someone help out?
if you are calling createFriendsPage in initState(), then it means that the code inside initState() is called only once, which is to build the UI.
If it's possible, I suggest that you call your createFriendsPage inside the override method build()
class FriendPage extends StatefullWidget{
//instantiate your state .. }
class FriendsPageState extends State<FriendPage> {
#override
Widget build(Build context) {
return cteateFriendsPage();
}
//other methods here ...
}

Resources