loading a page in background in Flutter - 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--;
}

Related

Dart - Re-using Flutter widgets sometime contains wrong data

I have a Flutter web application that displays multiple user profiles on a card within a Row. The cards can each flip over to reveal more information via this library:
https://pub.dev/packages/flip_card
The application uses WebSockets and receives a JSON list of user details which maps to a User dart class, and as soon as new list arrives on a socket, we create a widget and add it to a widgetList and wrap it in a setState():
webSocket.onMessage.listen((e) {
final List receivedJsonUserList = json.decode(e.data);
final List<User> userListFromSocket =
receivedJsonUserList.map((item) => User.fromJson(item)).toList();
userListFromSocket.forEach((newUser) {
setState(() {
widgets[newUser.user.id] = UserDetails(user: newUser);
widgetList = widgets.entries.map((entry) => entry.value).toList();
});
});
}
}
});
The widget is drawn like this:
#override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) => Scaffold(
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile
? NavigationDrawer()
: null,
backgroundColor: Colors.white,
body: Scrollbar(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widgetList),
],
),
),
),
),
);
}
The code works 90% of the time, but occasionally the wrong data is on the back of a card. So User 1 will have User 2's data on the back, etc.
Am I doing this correctly? Is there an obvious issue with this implementation? I tried to create a seperate widget for each user and it seems to resolve the issue but re-using widgets surely has to be possible.

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

How to pass text and images through multiple pages and preview in the final page in flutter?

I have 4 CreatePoll pages for my polling app.
CreatePoll1 is to add the questions and the options
CreatePoll2 is to add the caption, duration and category
CreatePoll3 is to add the images
CreatePoll4 is to preview all the details in the form of a card view, as it will look once it is posted.
Images: CreatePoll1 CretePoll2 CreatePoll3 CreatePoll4
How do I pass the data from all these CreatePoll pages and finally display it in the last preview page?
CreatePoll1 PageRoute:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreatePoll2(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
CreatePoll2 Page Route:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreatePoll3(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
CreatePoll3 Page Route:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreatePoll4(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
CreatePoll4 Page Route:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Nav(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
Complete Code for CreatePoll Pages:
https://docs.google.com/document/d/1Nz7Zk1PFzE3y4Zo_QbJoGTImrm_Q9AYHVvgy3KEtfYo/edit?usp=sharing
Update after looking at code:
I'll give you an example of doing this with a string value using GetX State Management. This can be done with any state management solution but I find this to be the fastest and easiest to use and I suggest checking it out if you're not familiar with it. Either way, its in your benefit to learn at least one of the solutions so that this type of stuff will be easier for you going forward. Provider or its updated version Riverpod would be another good one to start with.
Using this solution all your pages can become stateless because the text editing controllers are being initialized in in the PollDataController class.
For every unique textfield you have, create a dedicated text editing controller for it in the PollDataController (GetX Controller class) class below. Every value you need to display needs to be a variable in the PollDataController class. The example below is an RxString that listens to the text editing controller and its value is updated whenever the textfield is changed.
class PollDataController extends GetxController {
TextEditingController nameController;
RxString name = ''.obs; // adding .obs makes this a stream based observable String
// this onInit override can be used instead of initState in a stateful widget.
// All text editing controllers can be initialized here
#override
void onInit() {
super.onInit();
nameController = TextEditingController();
nameController.addListener(() {
name.value = nameController.text; // the RxString is automatically updated with whatever is input in the textfield
});
}
#override
void onClose() {
super.onClose();
nameController.dispose();
}
}
Initialize your GetX controller, this can be done on a page or on the start of your app, but it needs to be done before trying to use the controller so for simplicity, put this in your main method before running the app.
Get.put(PollDataController());
Then access variables in the GetX class from anywhere in the app by "finding" the initialized controller.
final pollDataController = Get.find<PollDataController>();
To access the already initialized text editing controller from that class:
final _nameController = pollDataController.nameController;
Then when you want to display the updated value in your UI, there are a few different ways to do this with GetX. Wrapping your text widget in an Obx widget is one of the ways. This widget will automatically be rebuilt anytime the value of the variable changes.
Obx(()=> Text(pollDataController.name.value)); // use .value at the end of the variable to access the actual value of the RxString
So whats happening here is anywhere you use that text editing controller in the textfield, the RxString name is updated with that value and you can access it from anywhere in the app using the method above.
So this will still require some work on your end but I gave you an example of doing this with a string, but all basic data types are supported ie RxInt, RxList etc...
Any piece of data you want to display on the last page can be accessed via the pollDataController.
When it comes to images, looks like you're using an external package, but generally I manage dynamic images with AssetImage() and the assetName (path) is just another RxString.
Let me know if you have any further questions.
Original Answer:
You’ll need to post more code than just your buttons if you want someone to write up a potential solution for you.
But to add to what #Agreensh said: The idea is you pick a state management solution, and each CreatePoll page populates the data on the separate state management class outside of the UI, without passing arguments from page to page.
Then by the time you get to your last page, the data is already populated and you just need to display it from the state management class. If you post the full code from your pages to show what’s going on with your text fields and drop down lists etc...I can try and help you out.

Flutter : using changeNotifier and provider when the context is not available

I'm trying to use the simple state management described in the Flutter docs, using a ChangeNotifier, a Consumer, and a ChangeNotifierProvider.
My problem is that I can't get a hold a on valid context to update my model (details below...). I get an error:
Error: Error: Could not find the correct Provider above this CreateOrganizationDialog Widget
This likely happens because you used a BuildContext that does not include the provider of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that CreateOrganizationDialog is under your MultiProvider/Provider.
This usually happen when you are creating a provider and trying to read it immediately.
Here are extracts of my code:
class OrganizationModel extends ChangeNotifier {
final List<Organization> _items = [];
/// An unmodifiable view of the items in the cart.
UnmodifiableListView<Organization> get items => UnmodifiableListView(_items);
void addList(List<Organization> items) {
_items.addAll(items);
notifyListeners();
}
}
This is my model.
class OrganizationBodyLayout extends StatelessWidget {
Future<void> _showCreateOrganizationDialog() async {
return showDialog<void>(
context: navigatorKey.currentState.overlay.context,
barrierDismissible: false,
child: CreateOrganizationDialog());
}
_onCreateOrganizationPressed() {
_showCreateOrganizationDialog();
}
_onDeleteOrganizationPressed() {
//TODO something
}
_onEditOrganizationPressed() {
//TODO something
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(mainAxisSize: MainAxisSize.max, children: [
ButtonBar(
alignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
RaisedButton(
onPressed: _onCreateOrganizationPressed,
child: Text("New Organization"),
),
],
),
Expanded(
child: Container(
color: Colors.pink,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: ChangeNotifierProvider(
create: (context) => OrganizationModel(),
child: OrganizationListView(),
)),
Expanded(child: Container(color: Colors.brown))
]))),
]));
}
}
A stateless widget that contains a ChangeNotifierProvider just on top of the list widget using the model.
On a button click, a modal dialog is shown, then data is fetched from the network. I should then update my model calling the addList operation.
Below is the code for the stateful dialog box.
class CreateOrganizationDialog extends StatefulWidget {
#override
_CreateOrganizationDialogState createState() =>
_CreateOrganizationDialogState();
}
class _CreateOrganizationDialogState extends State<CreateOrganizationDialog> {
TextEditingController _nametextController;
TextEditingController _descriptionTextController;
#override
initState() {
_nametextController = new TextEditingController();
_descriptionTextController = new TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 200,
height: 220,
child: Column(
children: [
Text('New organization',
style: Theme.of(context).textTheme.headline6),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
decoration: new InputDecoration(hintText: "Organization name"),
controller: _nametextController,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
decoration:
new InputDecoration(hintText: "Organization description"),
controller: _descriptionTextController,
),
),
ButtonBar(
children: [
FlatButton(
child: new Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: new Text("Create"),
onPressed: () {
setState(() {
Future<Organization> organization =
backendCreateOrganization(_nametextController.text,
_descriptionTextController.text);
organization.then((value) {
Future<List<Organization>> organizations =
backendReloadOrganizations();
organizations.then((value) {
var model = context.read<OrganizationModel>();
// var model = navigatorKey.currentState.overlay.context.read<OrganizationModel>();
//model.addList(value);
});
});
});
Navigator.of(context).pop();
//context is the one for the create dialog here
},
)
],
)
],
),
));
}
}
My problem happens at the line
var model = context.read<OrganizationModel>();
Thinking of it, the context available here is the modal dialog box context - so it's kind of logical that the Provider is not found in the widget tree.
However, I can't see how to retrieve the proper context (which would be the one for the result list view, where the Provider is located) in order to get the model and then update it.
Any idea is welcome :-)
Solved (kind of).
The only way I've found to solve this is by making my model a global variable:
var globalModel = OrganizationModel();
And referencing this global model in all widgets that consume it. I can't find a way to find the context of a stateless widget from within a callback in another stateful widget.
It works, but it's ugly. Still open to elegant solutions here :-)
Get_it seems to be elegant way of sharing models across the application. Please check the documentation for the different use cases they provide.
You could do something like the following
GetIt getIt = GetIt.instance;
getIt.registerSingleton<AppModel>(AppModelImplementation());
getIt.registerLazySingleton<RESTAPI>(() =>RestAPIImplementation());
And in other parts of your code, you could do something like
var myAppModel = getIt.get<AppModel>();

StatefulWidget using BlocBuilder doesn't rebuild on state change

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.