Flutter Feature Single Container in Widget Tree on first open - flutter

When the user opens the app for the first time i would like to explain some features. To do so i designed a bottom sheet which appears in the bottom part of the screen. At the same time i want to darken most of the screen, with the exception of the container I want to explain.
I attached a example picture below...
Thanks for any help!
class Feature extends StatefulWidget {
#override
_FeatureState createState() => _FeatureState();
}
class _FeatureState extends State<Feature> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (isFirstOpen)
showBottomSheet(context: context, builder: (context) => BottomInfo());
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemBuilder: (context, index) => Container(
padding: EdgeInsets.fromLTRB(50, 30, 50, 30),
),
itemCount: 3,
));
}
}

Update: OverlayEntry helped me to create those hints.

Related

Confetti package animation not displaying correctly upon scroll

I'm using Package Confetti 0.6.0 ConfettiWidget() within Card()s generated in a GridView.builder
Upon scroll the animation stops working properly & even vanishes if I scroll too much.
I thought the issue had to do with lazy loading, so I tried two other particle animation widgets:
Particles 0.1.4 and
Animated Background 2.0.0
Both of which worked fine, displaying correct animation - even through scrolling.
-> It could be just an issue with Package Confetti 0.6.0, but I think the problem might come from the way I'm initializing the ConfettiController and calling .play() method & disposing of it.
Here is my complete simplified code:
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late ConfettiController _controllerCenter;
#override
void initState() {
super.initState();
_controllerCenter =
ConfettiController(duration: const Duration(seconds: 1000));
_controllerCenter.play();
}
#override
void dispose() {
_controllerCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SafeArea(
child: new GridView.builder(
itemCount: 30,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Center(
//
child: ConfettiWidget(
confettiController: _controllerCenter,
shouldLoop: true,
),
//
),
);
},
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
),
),
);
}
}
-> I'd like to keep the animation running despite scrolling through the GridView.
I need to use a GridView.builder or GridView.count as data within the cards will be populated with Firebase Data.

How to avoid streambuilder executes unnecessarytimes

I'm trying to display a list of documents which works, but I read that one good practice is to manage states (which I'm trying currently to understand too). In this case every time I change of screen using the bottomNavigationBar the streamBuilder executes (I always see the CircularProgressIndicator).
I tried call the collection reference in the intState but still the same issue, my code:
class Deparments extends StatefulWidget {
Deparments({Key? key, required this.auth}) : super(key: key);
final AuthBase auth;
#override
_DeparmentsState createState() => _DeparmentsState();
}
class _DeparmentsState extends State<Deparments> {
late final Stream<QuerySnapshot<Object?>> _widget;
Stream<QuerySnapshot<Object?>> getProds(){
CollectionReference ref = FirebaseFirestore.instance.collection("Departamentos");
return ref.snapshots();
}
#override
void initState() {
super.initState();
_widget = getProds();
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: SideMenu(auth: widget.auth),
appBar: AppBar(
title: Text("Departamentos"),
centerTitle: true,
backgroundColor: Colors.green,
),
body: Container(
child: StreamBuilder<QuerySnapshot> (
stream: _widget,
builder: (BuildContext context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
List deparments =
snapshot.data!.docs.map((doc) => doc.id).toList();
return Column(
children: [
Expanded(
child: ListView.builder(
padding: EdgeInsets.only(top: 10),
scrollDirection: Axis.vertical,
itemCount: deparments.length,
itemBuilder: (context, index) {
return SingleChildScrollView(
child: Card(
child: Text(deparments[index]),
),
);
}),
)
],
);
}
}),
),
);
}
}
Update: for those who are facing the same issue Tayan provides a useful solution and he has a video showing the solution https://stackoverflow.com/a/64057210/9429407
Init state will not help you to avoid rebuilds because on changing tabs Flutter rebuilds your Screen. So we need some way to keep our screen alive, so here comes AutomaticKeepAliveClientMixin.
class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin<Home> {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
//Make sure to include the below method
super.build(context);
return SomeWidget();
}
}
The above implementation keeps all of your tab state persists and does not rebuilds the tabs again. Well this may serve your purpose but it may not be idle because this loads all the tabs at once even if the user actually didnt visited a tab, so to avoid the build unless a tab is clicked, use the above method in combination with pageview.
Check out pageView implementation
Also, if you want a better way to manage state and save some of your read calls to Firestore, then you should store data locally and fetch only those needed and/or use paginations.
Initialize your stream in initState just like this answer:
StreamBuilder being called numerous times when in build

Flutter - Using Dismissible on a PageView creates an ugly animation

with the following example code, is get a very ugly animation.
I would even say, it's no animation at all.
The next Page will just appear after the setstate is called.
How can I create a smooth delete animation using PageView?
If it is not possible via PageView, is there any alternative, that has the "snapping cards" feature?
Here is my code:
class SwipeScreen extends StatefulWidget {
const SwipeScreen({Key key}) : super(key: key);
static const routeName = '/swipe';
#override
_SwipeScreenState createState() => _SwipeScreenState();
}
class _SwipeScreenState extends State<SwipeScreen> {
List<String> content = ['one', 'two', 'three', 'four', 'five'];
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
scrollDirection: Axis.vertical,
itemCount: content.length,
controller: PageController(viewportFraction: 0.8),
itemBuilder: (context, index) {
return Dismissible(
key: ValueKey(content[index]),
child: Card(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
child: Text('test'),
),
),
onDismissed: (direction) {
setState(() {
content = List.from(content)..removeAt(index);
});
},
);
},
),
);
}
}
Replacing PageView.builder() with ListView.builder() will create a smoother animation.
Hopefully this is what you're looking for!
Unfortunately, the PageView widget is not intended to be used with the Dismissible widget as the animation when the dismiss is complete is not implemented.
You can still change your PageView to a ListView and set a physics to PageScrollPhysics() to get the animation on dismiss but you will probably encounter some other issues on Widget sizes

How to update content of a ModalBottomSheet from parent while active

I have a custom button which takes in a list of items. When it is pressed, it opens a modal bottom sheet and passes that list to the bottom sheet.
However, When the buttons items change it doesn't update the items in the bottom sheet.
How can I achieve this effect.
Simple example
ButtonsPage
|
Button(items: initialItems)
|
BottomSheet(items: initialItems)
** After a delay, setState is called in ButtonsPage with newItems, thereby sending newItems to the button
ButtonsPage
|
Button(items: newItems)
| ## Here, the bottom sheet is open. I want it to update initialItems to be newItems in the bottom sheet
BottomSheet(items: initialItems -- should be newItems)
Code Sample
This is my select field which, as shown, receives a list items and when it is pressed it opens a bottom sheet and sends the items received to the bottom sheet.
class PaperSelect extends StatefulWidget {
final List<dynamic> items;
PaperSelect({
this.items,
}) : super(key: key);
#override
_PaperSelectState createState() => _PaperSelectState();
}
class _PaperSelectState extends State<PaperSelect> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.disabled ? null : () => _showBottomSheet(context),
child: Container(
),
);
}
void _showBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => BottomSheet(
items: widget.items,
),
)
);
}
}
After some time (a network call), items is updated in the parent Widget of PaperSelect. PaperSelect then updates and receives the new items.
class BottomSheet extends StatefulWidget {
final dynamic items;
BottomSheet({
this.items,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _BottomSheetState();
}
}
class _BottomSheetState extends State<BottomSheet> {
dynamic items;
#override
void initState() {
print(widget.items);
items = widget.items;
super.initState();
}
#override
Widget build(BuildContext context) {
if(items==null) return Center(
child: SizedBox(
width: 140.0,
height: 140.0,
child: PaperLoader()
),
);
if(items==-1) return Text("Network Error");
return Column(
children: <Widget>
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (BuildContext context, int i) => ListTile(
onTap: () => Navigator.pop(context),
title: Text('')
)
),
),
],
);
}
}
Now, I want to send the updated data to the bottom sheet. However, it doesn't work because the ModalBottomSheet is already open.
How can I get around this?
I assume here that items is a List<String>, since you did not specify that at all. (You should generally not use dynamic in most cases, because it does not do any type checking at all). Anyway,
One thing you could do (beside countless others) is pass in a ValueNotifier<List<String>> instead of a List<String> and then user that with a ValueListenableBuilder in your bottom sheet. like:
// in the caller
final itemsNotifier = ValueNotifier(items);
// in your bottom sheet
ValueListenableBuilder<List<String>>(
valueListenable: itemsNotifier,
builder: (context, snapshot, child) {
final items = snapshot.data;
// build your list
},
)
then every time you would change itemsNotifier.value = [...] your items would rebuild.
Note when updating itemsNotifier.value: It must not be done inside build/didUpdateWidget/etc. so if this is the case you might use WidgetsBinding.instance.addPostFrameCallback(() => itemsNotifier.value = ... ) to postpone updating the value until after the current build phase.

How do i put video in listview builder?

i want to add a video player to my Listview.builder(child:???).Every video must have different links how can i do that?
ListviewBuilder
> buildSectionTitle(context, 'Videos'),
buildContainer(
ListView.builder(
itemBuilder: (ctx, index) => Container(
color: Theme.of(context).accentColor,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 10,
),
child: ???,
),
),
),
1st step: Add video player plugin
2nd step: create a new widget containing vieo(for each element of your ListView
class VideoItem extends StatefulWidget {
final string url;
VideoItem(this.url);
#override
_VideoItemState createState() => VideoItem();
}
class _VideoItemState extends State<VideoApp> {
VideoPlayerController _controller;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.network(widget.url)
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Center(
child: _controller.value.initialized
?Stack(childre:[ AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),),
Center(child;:GestureDetector(onTap:_playPause,
child:Icon(Icons.play_circle))
]
)
: Container(),
),
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
_playPause(){
if(_controller.isPlaying){
_controller.pause();
}else{
_controller.play();
}
}
Then : Use it in ListView
ListView.builder(
itemCount: listOfVideoUrl.length, //Notice this
itemBuilder: (ctx, index) => VideoItem(listOfVideoUrl[index]))
NOTE: This is not the best way to achieve this . consider using images in ListViewthen open player page after users click