I tried to load data from the database by using FutureBuilder like this code.
// counter application
import 'package:flutter/material.dart';
class SetStateScreen extends StatefulWidget {
#override
_SetStateScreenState createState() => _SetStateScreenState();
}
class _SetStateScreenState extends State<SetStateScreen> {
Future<int> getData() async {
int initNumFromDB = 0; // fetch some asynchronous initial value
return initNumFromDB;
}
#override
Widget build(BuildContext context) {
int counter;
return Container(
color: Colors.white,
child: FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
Widget childWidget;
if (snapshot.hasData) {
counter = snapshot.data;
childWidget = Center(
child: Container(
child: FlatButton(
onPressed: () {
setState(() {
counter += 1;
});
},
child: Text("$counter"),
)),
);
} else {
childWidget = Container();
}
return childWidget;
}),
);
}
}
But, when pushing the button and running the setState method, FutureBuilder loads initial data again and reset the counter number. How to load initial data once, and run this code correctly?
Try this is the proper way to use
class SetStateScreen extends StatefulWidget {
#override
_SetStateScreenState createState() => _SetStateScreenState();
}
class _SetStateScreenState extends State<SetStateScreen> {
Future<int> getData() async {
int initNumFromDB = 0; // fetch some asynchronous initial value
return initNumFromDB;
}
#override
void initState() {
super.initState();
getData();
}
}
You need to call you future method inside the initstate method . As such it will run only once when the widget is built and not everytime when setstate() is called.
Related
I have CounterProvider mixin with ChangeNotifier and inside the class i have two counters (_counterOne and _counterTwo) when _counterOne is inremented all Consumers are executed hence Widgets that consume _counterOne and _counterTwo are executed. But I want only the _counterOne consumer widget to execute.
I couldn't found any salution to do that.
Thanks in advance.
Provider:
class CounterProvider with ChangeNotifier {
int _counterOne = 1;
int getCounterOne() => _counterOne;
void incrementCounterOne() {
_counterOne++;
notifyListeners();
}
int _counterTwo = 2;
int getCounterTwo() => _counterTwo;
void incrementCounterTwo() {
_counterTwo++;
notifyListeners();
}
}
View:
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
#override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
late CounterProvider _provider;
#override
void initState() {
_provider = Provider.of<CounterProvider>(context, listen: false);
super.initState();
}
#override
Widget build(BuildContext context) => Scaffold(
body: Column(
children: [
Consumer<CounterProvider>(builder: (context, value, child) {
print("Consumer of CounterOne executed");
return Text("CounterOne: ${value.getCounterOne()}");
}),
Consumer<CounterProvider>(builder: (context, value, child) {
print("Consumer of CounterTwo executed");
return Text("CounterTwo: ${value.getCounterTwo()}");
}),
// Buttons
ElevatedButton(
onPressed: () {
_provider.incrementCounterOne();
},
child: const Text("Increment CounterOne"),
),
ElevatedButton(
onPressed: () {
_provider.incrementCounterTwo();
},
child: const Text("Increment CounterTwo"),
),
],
));
}
according to how Provider works if you use Consumer on a Provider class it will listen to any changes on that class and update every time there is change, To solve this you can use Selector as explained in here.
But ValueListenableBuilder also works fine as suggested in the comments.
I was trying to make a videoplayer that can be paused by tapping the screen (like YouTube Short)
but I was having a tough time working with Provider as I'm still new to it I was unable to access methods in VideoPlayerScreen so I decided to make a VideoPlayerProvider class so I can update the URL attribute inside it through other classes but I'm getting the ProviderNotFound Exception
Can someone guide me what am I doing wrong here? Any Resources for learning provider in depth would be helpful too, Thanks in Advance.
Apologies if I was unable to frame my question properly
`
import 'dart:async';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main() => runApp(ChangeNotifierProvider(
create: (context) => VideoPlayerProvider(), child: const VideoPlayerApp()));
class VideoPlayerProvider extends ChangeNotifier {
String url =
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
late VideoPlayerController _controller = VideoPlayerController.network(url);
String get getUrl => url;
VideoPlayerController get getController => _controller;
void newVid(String newUrl) {
url = newUrl;
notifyListeners();
}
void pp() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
notifyListeners();
}
}
class VideoPlayerApp extends StatelessWidget {
const VideoPlayerApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Player Demo',
home: Stack(children: [ VideoPlayerScreen()]),
);
}
}
class VideoPlayerScreen extends StatefulWidget{
const VideoPlayerScreen({super.key});
#override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen>{
late VideoPlayerController _controller =
context.watch<VideoPlayerProvider>().getController;
late Future<void> _initializeVideoPlayerFuture;
late String _url = context.watch<VideoPlayerProvider>().getUrl;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.network(_url);
_initializeVideoPlayerFuture = _controller.initialize();
_controller.setLooping(true);
_controller.play();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return AspectRatio(
aspectRatio: 9 / 18.45,
child: VideoPlayer(_controller),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
`
I was trying to update the URL of VideoPlayerScreen So I can update it from external methods
Your code contained redundant initialization of variables and improper usage of the Provider. For a better understanding, you are suggested to go back to the documentation and examples of the Provider package.
However, I've removed all the redundancy from the code and tried to present a better usage of the Provider to you.
Revised code has been pasted below. I've tested it with the latest versions of the VideoPlayer and Provider packages and it is working perfectly fine.
I've mentioned step wise detailed comments inside the code for you to understand what changes have been made and why. Some bonus steps have also been added and explained therein.
You would notice that the provider is being fully used to initialize the controller and play, pause and change the video.
Remember, there can be better ways of achieving this; however, for simplicity and relevance, your code has been modified wherever necessary, in order to achieve the required objective.
void main() => runApp(ChangeNotifierProvider(
create: (context) => VideoPlayerProvider(), child: const VideoPlayerApp()));
class VideoPlayerProvider extends ChangeNotifier {
String url =
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
late VideoPlayerController _controller;
/// STEP 1
/// Defined this async method for use in the FutureBuilder. It contains the
/// code required to initialize the controller and play the video.
Future<void> initializeController() async {
_controller = VideoPlayerController.network(url);
await _controller.initialize();
await _controller.setLooping(true);
return _controller.play();
}
String get getUrl => url;
VideoPlayerController get controller => _controller;
/// BONUS STEP 3
/// Removed the unnecessary notifyListeners() method and instead called
/// initializeController() in order to load and play the new video.
void newVid(String newUrl) {
url = newUrl;
initializeController();
// notifyListeners();
}
/// BONUS STEP 2
/// Removed the unnecessary notifyListeners() method.
/// Since we are using the same instance of controller inside the widget, we
/// don't need to define and notify the listeners
void pp() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
// notifyListeners();
}
}
class VideoPlayerApp extends StatelessWidget {
const VideoPlayerApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Player Demo',
home: Stack(children: const [VideoPlayerScreen()]),
);
}
}
class VideoPlayerScreen extends StatefulWidget {
const VideoPlayerScreen({super.key});
#override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
late VideoPlayerProvider provider;
/// STEP 4
/// Used the same instance of controller as initialized in the provider
VideoPlayerController get controller => provider.controller;
#override
void initState() {
super.initState();
/// STEP 2
/// Lazy initialized the provider variable, for later use in the app
provider = Provider.of<VideoPlayerProvider>(context, listen: false);
/// STEP 3
/// Removed all redundant code for initializing the controller
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
/// STEP 5
/// Used the provider's async method here instead of creating a new
/// redundant one here
future: provider.initializeController(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return AspectRatio(
aspectRatio: 9 / 18.45,
/// BONUS STEP 1
/// Used a GestureDetector widget to listen to screen taps and
/// called the pp() toggle method of the provider.
child: GestureDetector(
onTap: () => provider.pp(),
child: VideoPlayer(controller),
),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
Hope it helps!
I have a Listview.builder inside a FutureBuilder which taking some data from an http request.i have a bool closed i want to prevent some items from refreshing if status bool is true
how can I do that
You can achieve this by placing your call in initState. In this way you can make sure that it will get the data only once.
example:
class FutureSample extends StatefulWidget {
// Create instance variable
#override
_FutureSampleState createState() => _FutureSampleState();
}
class _FutureSampleState extends State<FutureSample> {
Future myFuture;
Future<String> _fetchData() async {
await Future.delayed(Duration(seconds: 10));
return 'DATA';
}
#override
void initState() {
// assign this variable your Future
myFuture = _fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FutureBuilder(
future: myFuture,
builder: (ctx, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.toString());
}
return CircularProgressIndicator();
},
),
),
);
}
}
In that way you don't need a bool value. There are also different ways to achieve or extend your request. You can check this article for more informations: https://medium.com/flutterworld/why-future-builder-called-multiple-times-9efeeaf38ba2
Below code always show OnboardingScreen a little time (maybe miliseconds), after that display MyHomePage. I am sure that you all understand what i try to do. I am using FutureBuilder to check getString method has data. Whats my fault ? Or any other best way for this ?
saveString() async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('firstOpen', '1');
}
getString() method always return string.
getString() async {
final prefs = await SharedPreferences.getInstance();
String txt = prefs.getString('firstOpen');
return txt;
}
main.dart
home: new FutureBuilder(
future: getString(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
})
Usually I'm using another route, rather than FutureBuilder. Because futurebuilder every hot reload will reset the futureBuilder.
There always will be some delay before the data loads, so you need to show something before the data will load.
Snapshot.hasData is showing only the return data of the resolved future.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
const isOnboardingFinished = 'isOnboardingFinished';
class _SplashScreenState extends State<SplashScreen> {
Timer timer;
bool isLoading = true;
#override
void initState() {
_checkIfFirstOpen();
super.initState();
}
Future<void> _checkIfFirstOpen() async {
final prefs = await SharedPreferences.getInstance();
var hasOpened = prefs.getBool(isOnboardingFinished) ?? false;
if (hasOpened) {
_changePage();
} else {
setState(() {
isLoading = false;
});
}
}
_changePage() {
Navigator.of(context).pushReplacement(
// this is route builder without any animation
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return isLoading ? Container() : OnBoarding();
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(child: Text('homePage'));
}
}
class OnBoarding extends StatelessWidget {
Future<void> handleClose(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool(isOnboardingFinished, true);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
onPressed: () => handleClose(context),
child: Text('finish on bording and never show again'),
),
),
);
}
}
From the FutureBuilder class documentation:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
So you need to create a new Stateful widget to store this Future's as a State. With this state you can check which page to show. As suggested, you can start the future in the initState method:
class FirstPage extends StatefulWidget {
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final Future<String> storedFuture;
#override
void initState() {
super.initState();
storedFuture = getString();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: storedFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
});
}
}
So in your home property you can call it FirstPage:
home: FirstPage(),
Your mistake was calling getString() from within the build method, which would restart the async call everytime the screen gets rebuilt.
I'm trying to use FutureBuilder but i'm countering an issue, my future is called but not executed. And i couldn't understand why. Does someone has an idea why ?
This is my code:
class TesPage extends StatefulWidget {
#override
_TesPageState createState() => _TesPageState();
}
class _TesPageState extends State<TesPage> {
Future<String> future;
Future<String> futureFunction() async {
try {
await text();
return "Test"; // this is never called
} catch (e) {
return e.toString();
}
}
#override
#mustCallSuper
void initState() {
future = futureFunction();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new FutureBuilder<String>(
future: future,
builder: (BuildContext context, snapshot) {
// returning a widget based on the future
},
),
);
}
}
Thank you !
Update
fix my code example
You are not properly waiting for a Future to be resolved.
I suggest you to do the following way:
class TesPage extends StatefulWidget {
#override
_TesPageState createState() => _TesPageState();
}
class _TesPageState extends State<TesPage> {
//Future<String> future;
Future<String> futureFunction() async {
try {
await text();
return "Test"; // this is never called
} catch (e) {
return e.toString();
}
}
//#override
//#mustCallSuper
//void initState() {
// future = futureFunction();
// super.initState();
//}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new FutureBuilder<String>(
future: futureFunction(),
builder: (BuildContext context, snapshot) {
// returning a widget based on the future
},
),
);
}
}
I've commented the unused statements and variables
This way the FutureBuilder will invoke the futureFunction and wait for its result.
Be aware of the states snapshot can be in, check this for complete informations.
Usually you can just check if snapshot.hasData or not, but the class offers you more information about the status of the async computation (aka the futureFunction in this case).