StatefulWidget using BlocBuilder doesn't rebuild on state change - flutter

I'm using Flutter (BloC pattern) to build a widget that streams multiple camera feeds. When a feed is clicked on, it is shown in the main feed (think Google hangouts, where the face of whoever is speaking is shown front and centre, and others are smaller at the side).
When trying to switch the selected feed, the state (int) gets yielded with the expected new value by the Bloc's MapEventToState; however, the main CamFeed widget doesn't switch feeds.
My understanding of this usage of a StatefulWidget that returns a BlocBuilder<CamSwitcherBloc, int> should rebuild when that int is changed. Instead, nothing seems to be happening. build() is only getting called when it first gets created, and when the state of one of the children within CamFeed is getting updated.
I've confirmed via observatory that there is, as expected, only one instance of the CamSwitcherBloc.
So - am I wrong in thinking that:
When MapEventToState inside my Bloc yields a new value (selectedCam, type in the builder below), build() should be called for my widget.
If that is correct, any suggestions as to where to continue my hunt would be greatly appreciated.
Thanks!
class CamSwitcher extends StatefulWidget {
#override
_CamSwitcherState createState() => _CamSwitcherState();
}
class _CamSwitcherState extends State<CamSwitcher> {
#override
Widget build(BuildContext context) {
final camSwitcherBloc = BlocProvider.of<CamSwitcherBloc>(context);
return BlocBuilder<CamSwitcherBloc, int>(
builder: (context, selectedCam) => Stack(
children: <Widget>[
Container(
width: 1200,
height: 800,
child: CamFeed(
topic: camSwitcherBloc.cameras[selectedCam],
),
),
Row(
children: <Widget>[
Container(
width: 180,
height: 120,
child: CamFeed(
topic: CamSwitcherBloc.cam1,
focuser: () {
camSwitcherBloc.dispatch(ExpandCamera(0));
},
),
),
Container(
width: 180,
height: 120,
child: CamFeed(
topic: CamSwitcherBloc.cam2,
focuser: () {
camSwitcherBloc.dispatch(ExpandCamera(1));
},
),
),
],
),
],
),
);
}
}

Turns out this had to do with the CamFeed widget. Upon switching, it was indeed subscribing to the new feed, but wasn't unsubscribing from the old one. This is symptom of the way CamFeed works, and is not likely to be useful for other readers.
Perhaps relevant for others:
In this case, while CamSwitcherBloc was not rebuilding (though I expected it would), its child (Camfeed) actually was, as expected.

Related

loading a page in background in Flutter

I need to load some data in a global variable in Flutter every minute.
To load the data (which is a list) to my global data, I need to open another page. But how can I open that page in the background because I do not need that page, I just want to get some data from it.
create a different class and write a function that instantiates a list from itself
Loading a Page is something related to UI or Presentation layer but loading your data is related to Business layer, you need to keep separate them from each other
There is a topic known as State Management, you should centralize your data providers to a separate layer and change your Presentation layer based on the State of your data
First of all take a look at this link, here is an example of using Provider pattern to manage different State of your data
Then you can use some more complicated libraries like BLOC library for State Management
(More of a workaround)
As I wrote here one option is to use Stack widget as a page loader.
Each "page" expand on the entire screen.
When you want to show the next "page" replace the front layer in the stack with SizedBox.
So all the elements are actually randerd at the same time but will not be visible.
For example, the video on the "second page" will start getting loaded even when the user is on the "first page" and will be ready for the user when he continues.
One way to do that is by using get as state management.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PageWithLayers extends StatelessWidget {
const PageWithLayers({super.key});
#override
Widget build(BuildContext context) {
final TestController c = Get.put(TestController());
return Stack(
children: [
Container(
color: Colors.red,
child: FutureBuilder<Widget>(
future: Future(() async {
await Future.delayed(const Duration(seconds: 4));
return const Text('Done loading in the background');
}),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return snapshot.requireData;
}
return const Text('Loading Video');
},
),
),
Obx(
() => c.toShowTopLayer > 0
? Container(
color: Colors.blue,
width: double.infinity,
height: double.infinity,
child: Center(
child: TextButton(
onPressed: c.removeTopLayer,
child: const Text('Next'),
),
),
)
: const SizedBox(),
),
],
);
}
}
class TestController extends GetxController {
var toShowTopLayer = 1.obs;
removeTopLayer() => toShowTopLayer--;
}

How to require child widget parent to be only particular widget in Flutter?

Is it possible to force some widget to be child only of some particular widget, for example, I have ChildWidget() and I want this widget to be a child of Stack only, otherwise if the direct parent of this widget is not Stack then throw compilation time error, like when the required parameter of the widget is not initialized/passed?
Example of the desired end result:
import 'package:flutter/material.dart';
class WidgetName extends StatelessWidget {
const WidgetName({ Key? key }) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 50,
width: 50,
child: Placeholder()
),
ChildWidget(), //Everything is okay, child's direct parent is Stack
Container(
height: 50,
width: 50,
child: ChildWidget(), // Compilation-time error (red underline), because direct ansector is Container, instead of Stack.
),
],
);
}
}
Thanks in advance!
Here is an implementation of the requested feature. However, only run -time error was implemented if someone knows how compilation-time error can be reproduced, please share you solution, will be appreciated.
Although visitAncestorElements method is relatively expensive O(N), we are returning false as return value, which means we are checking only 1st element, and the time complexity reduces to O(1).
context.visitAncestorElements((element) {
assert(element.widget.runtimeType.toString().toLowerCase() != directAnsector.toLowerCase(),"Ansector Error");
return false;
});

Mouse scroll stuck while cursor is on top of youtube_player_iframe

Mouse scroll stuck on top of video. I'm using youtube_player_iframe. Also, I don't want to rebuild the iframe widget. I tried to wrap it with pointer_interceptor but it didn't solve the problem. My first priority is to solve the scroll issue and avoid rebuilding the widget on scrolling. Wrapping everything on SingleChildScrollView is not a good practice.
I don't want to use YouTube API like this package
Need to implemented flutter-web
if you have an alternative way to handle it, feel free to share.
Thanks
check this output video
Test widget
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:sliver_tools/sliver_tools.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
class YoutubeVideoAdTestScreen2 extends StatefulWidget {
YoutubeVideoAdTestScreen2({Key? key}) : super(key: key);
#override
_YoutubeVideoAdTestScreen2State createState() =>
_YoutubeVideoAdTestScreen2State();
}
class _YoutubeVideoAdTestScreen2State extends State<YoutubeVideoAdTestScreen2> {
YoutubePlayerController _controller = YoutubePlayerController(
initialVideoId: '1oF3pI5umck',
params: YoutubePlayerParams(
// Defining custom playlist
startAt: Duration(seconds: 30),
showControls: true,
showFullscreenButton: true,
),
);
#override
void dispose() {
super.dispose();
_controller.close();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
MultiSliver(
children: [
...List.generate(
4,
(index) => Container(
color: index % 2 == 0 ? Colors.amber : Colors.cyanAccent,
height: index * 50 + 100,
),
).toList(),
SliverToBoxAdapter(
child: YoutubePlayerIFrame(
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{},
controller: _controller,
aspectRatio: 16 / 9,
),
),
...List.generate(
4,
(index) => Container(
color: index % 2 == 0 ? Colors.amber : Colors.cyanAccent,
height: index * 50 + 100,
),
).toList(),
],
),
],
),
);
}
}
The Problem with embedding HTML elements in your Flutter App is, that these are rendered differently. The dart api provides this information about iframes and their pointer events:
Due to security restrictions with cross-origin iframe elements, Flutter cannot dispatch pointer events to an HTML view. If an iframe is the target of an event, the window containing the is not notified of the event. In particular, this means that any pointer events which land on an iframe will not be seen by Flutter, and so the HTML view cannot participate in gesture detection with other widgets.
There is no ideal solution to this, either losing the ability to scroll when hovering the iframe, or to lose the ability to interact with the iframe, but still scroll over it.
The idea is simple: Wrap another AspectRatio in a PointerInterceptor, inside a Stack, so that the scrolling behaviour is still provided, but you sadly cannot interact with the iframe any more.
.....
SliverToBoxAdapter(
child: Stack(
children: [
YoutubePlayerIFrame(
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{},
controller: _controller,
aspectRatio: 16 / 9,
),
PointerInterceptor(
child: AspectRatio(
aspectRatio: 16 / 9,
),
),
],
),
),
......
There are surely different ways to implement this, butI found this to be the easiest if you just want to scroll over the iframe. The player can still be controlled via its _controller, so play(), stop() etc. still seem to work.
EDIT:
The PointerInterceptor is a pub.dev package: https://pub.dev/packages/pointer_interceptor

Save values of variables while navigation in Flutter

I am new to Flutter and I am trying to save the value of "counter" on first_screen when I navigate to second_screen and after that I want to save the value of "secondCounter" on second_screen when I navigate to first_screen. The "counter" and "secondCounter" value resets to 0 when I navigate between the two screens but I want to save the values of them. My code is as follows :
main.dart :-
import 'package:flutter/material.dart';
import 'package:provider_practice/screens/first_screen.dart';
import 'package:provider_practice/screens/second_screen.dart';
void main() {
runApp(MaterialApp(
home: FirstScreen(),
routes: {
"/first" : (context) => FirstScreen(),
"/second" : (context) => SecondScreen(),
},
));
}
first_screen :-
import 'package:flutter/material.dart';
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You pressed the button $counter times."),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
setState(() {
counter++;
});
},
child: Text("Click Me"),
),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/second");
},
child: Text("Go to Second"),
),
],
),
),
),
);
}
}
second_screen.dart :-
import 'package:flutter/material.dart';
class SecondScreen extends StatefulWidget {
#override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
int secondCounter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You pressed the button $secondCounter times."),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
setState(() {
secondCounter++;
});
},
child: Text("Click Me"),
),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/first");
//Navigator.pop(context);
},
child: Text("Go to First"),
),
],
),
),
),
);
}
}
I am not sure if you specifically you need it to reset when the app relaunches or not but if it is fine if the value is preserved when you relaunch the app then here are a few options. Either way, you can reset the value when the app launches manually by setting it back to 0.
The first and simplest way is to use the answer in this comment. If you have both widgets in an IndexedStack (read more here) and then have the button change the stack index that would work but you would lose the benefit of page transition animations and this is a less performant option as your app grows because flutter has to run both widgets at the same time even if one isn't being used.
A second more performant way you can do this is through the Shared Preferences package. This would save it to the disk so you would need to reset it every time you launch the app if you want it to be 0 every time you open the app.
A third way is to use an external database such as Firebase. Firebase offers both their "Realtime Database" and their newer "Cloud Firestore" as well as their authentication services all for free so it might be an option you want to look into for building apps in the future. I would recommend Firestore over the real time database because it is newer and I prefer it personally. This option would also need you to reset the counter when launching the app but that shouldn't be too big of a problem.
Another way you can do this (this won't preserve state when relaunching the app) is to use the Provider Package. This package was endorsed by Flutter and is the recommended way to manage state. If you add a provider at the root of your app then it will be preserved and it can store both the first and second counter for you. Provider has a bit of a learning curve so I would recommend you look into it a bit.
Here are two videos which helped me get started with Provider:
https://youtu.be/O71rYKcxUgA
https://youtu.be/MkFjtCov62g
I'd recommend you watch them both as they are by the same person and one is an introduction to what Provider is and the other shows you how to use it. The second video has a similar example to your use case but I'd recommend you still watch both.
Hope this helps. Please let me know if this answered your question or if you need any more help or clarification please let me know.
This is easy to implement and there are a few ways you can do it.
One way is to pass it in as a parameter.
If you add the counter variable to be inside of the FirstScreen/SecondScreen widgets, you can then add them to the constructor.
Example:
class FirstScreen extends StatefulWidget {
int counter;
FirstScreen(counter);
#override
_FirstScreenState createState() => _FirstScreenState();
}
Then in your state's body you would change the text to Text("You pressed the button ${widget.counter} times.") and the setState function to setState(() {widget.counter++;});
You would do the same in the second widget making a parameter called counter or whatever you want and then make a constructor. You can also make it required or set it to have a default of 0 if it is not passed through.
Finally, to pass it through to the second widget you can just use Navigator.push(context, SecondScreen(widget.counter) and vice versa. This however, won't let you use named routes.
Another approach is to use arguments and named routes. I think this will suit your use case better.
In both of your screens where you navigate, just add an arguments parameter and pass in the counter Navigator.pushNamed(context, 'routePath', arguments: counter);. (P.S. You don't have to name the counters as firstCounter and secondCounter, they can both be called counter since they are in different widgets). Then just add to both widgets counter = ModalRoute.of(context).arguments. You also don't need to wrap your counter value in curly braces ({}). In the vide he needed the data as a map so he did that, but you just want a number. Hope this helps.
Here is a video I found which explains how to pass arguments in named routes if you find the text confusing. For context, this is a video series teaching Flutter and the app he is currently building is a world time app. Video Link.
If you are interested in the entire course here is the Video Playlist

Queries on Flutter ListView

Im learning about listviews and I have the below two dart files, one using ListView builder and the other Listview. Both output the same result. I have been following the listview guide: https://pusher.com/tutorials/flutter-listviews
Below are my queries on listview:
I understand in the real world the data will be coming from an API and wanted to know which of the below options will be used and why?
Am i correct to understand that any widget like container, text can be child of within a listView?
In option 1 the ListView child is a function _buildListItemsFromLocation(). Is this a good practise or should we move the _buildListItemsFromLocation()code to a separate dart file?
Option1: ListView
class LocationListView extends StatefulWidget {
#override
_LocationListViewState createState() => _LocationListViewState();
}
class _LocationListViewState extends State<LocationListView> {
List<Container> _buildListItemsFromLocation() {
int index = 0;
return locationData.map((location) {
var container = Container(
child: Row(
children: [
Container(
margin: EdgeInsets.all(10.0),
child: Image(
image: AssetImage(location.imagePath),
width: 100.0,
height: 100.0,
fit: BoxFit.cover,
),
),
Container(
child: Text(location.name),
)
],
),
);
return container;
}).toList();
}
#override
Widget build(BuildContext context) {
return ListView(
children: _buildListItemsFromLocation(),
);
}
}
Option 2 - ListView.builder
class LocationList extends StatefulWidget {
#override
_LocationListState createState() => _LocationListState();
}
class _LocationListState extends State<LocationList> {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: locationData.length,
itemBuilder: (context, index) {
return Row(
children: [
Container(
margin: EdgeInsets.all(10.0),
child: Image(
image: AssetImage(locationData[index].imagePath),
width: 100.0,
height: 100.0,
fit: BoxFit.cover,
),
),
Container(
child: Text(locationData[index].name),
)
],
);
}
);
}
}
I use method 2 because it is easy to understand and follows the order from top to bottom so it is easy to read the code.
Any widget can be a child of another widget. Depending on how and what you use them for.
Many people say that we should create another class and then call it again rather than split as above because it affects the performance of the app. In case of using a lot but only in one screen, you can use the same method as your own.
The answer may be flawed, have nothing to give yourself.
If you don't know in advance list size, then create it through builder
1.1 If you create list and you know that elements count won't be more than ten or
twelve, you can create ListView from example1
Any widget can be in ListView. For convenience there is widget called
ListTile, which contains leading, trailing, title, subtitle widgets
its's ok