How to use dart.core.sink in flutter - flutter

import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
//Using Bloc
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.darkThemeEnabled,
initialData: false,
builder: (context, snapshot) => MaterialApp(
theme: snapshot.data ? ThemeData.dark() : ThemeData.light(),
home: HomePage(snapshot.data)),
);
}
}
class HomePage extends StatelessWidget {
final bool darkThemeEnabled;
HomePage(this.darkThemeEnabled);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dynamic Theme"),
),
body: Center(
child: Text("Hello World"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Dark Theme"),
trailing: Switch(
value: darkThemeEnabled,
onChanged: bloc.changeTheme,
),
)
],
),
),
);
}
}
class Bloc {
final _themeController = StreamController<bool>();
get changeTheme => _themeController.sink.add;
get darkThemeEnabled => _themeController.stream;
}
final bloc = Bloc();
1.A warning says to Close instances of dart.core.sink
2.Why dart.core.sink is used in flutter?
3.How can I solve this error
4.Its error documentation redirects me to this website link
5.I don't know how to use these methods in flutter please guide me

dart.core.sink is an interface that is implemented by Stream.
The warning is showing, because the dart compiler wants you to .close() your instance of a Stream. In this case that is your final _themeController = StreamController<bool>().
If you want to fix the warning, add
void dispose() {
_themeController.close();
}
to your Bloc class.
Just adding the method is not doing much, since it's not called. So you should change your main() method to call bloc.dispose() after runApp(MyApp()).

That error occur when missing close StreamController.
Simple way to fix:
Create abstract class:
abstract class Bloc {
void dispose();
}
Your bloc class implements Bloc, now you can close StreamController in dispose:
class ColorBloc implements Bloc {
// streams of Color
StreamController streamListController = StreamController<Color>.broadcast();
// sink
Sink get colorSink => streamListController.sink;
// stream
Stream<Color> get colorStream => streamListController.stream;
// function to change the color
changeColor() {
colorSink.add(getRandomColor());
}
// Random Colour generator
Color getRandomColor() {
Random _random = Random();
return Color.fromARGB(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
// close Stream
#override
void dispose() {
streamListController.close();
}
}

Related

Flutter - Accessing a Provider from a higher point in the Widget tree

I've been working with Flutter recently, and I saw that there was many ways to deal with state management.
Following the recommendations there, I've been using Provider to deal with the state of my app.
I can update a part of my state from one of the widgets in my UI. To do that, I can call a method of the provider that's above the current widget in the context. No problems with this.
But I want the update of my state to be made from an overlay.
The issue is: When I'm inserting an OverlayEntry with Overlay.of(context)?.insert(), it inserts the overlayEntry to the closest Overlay, which is in general the root of the app, which is above the ChangeProvider. As a result, I get an exception saying I can't find the Provider from the OverlayEntry.
Here is a replication code I've been writting:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => NumberModel(), // All widgets that will be lower in the widget tree will have access to NumberModel
child: NumberDisplayer()
),
);
}
}
// Simple ChangeNotifier. We have a number that we can increment.
class NumberModel extends ChangeNotifier {
int _number = 10;
int get number => _number;
void add_one() {
_number = number + 1;
notifyListeners();
}
}
// This class displays a number, and a button.
class NumberDisplayer extends StatelessWidget {
NumberDisplayer({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var overlayEntry = OverlayEntry(builder: (context) =>
Positioned(
top: 100,
left: 50,
child: FloatingActionButton(onPressed: (){
// Throws "Error: Could not find the correct Provider<NumberModel> above this _OverlayEntryWidget Widget"
Provider.of<NumberModel>(context, listen: false).add_one();
})));
return Consumer<NumberModel>(
builder: (context, numberModel, child) {
return Column(
children: [
Text('Number: ${numberModel.number}'),
FloatingActionButton(onPressed: () {
Overlay.of(context)?.insert(overlayEntry);
})
],
);
},
);
}
}
I would like to find a way to update the information in my provider from the overlay, but I'm not sure how to approach this problem.
Thanks for your help everyone !

Where do you place the Widget cache when using Provider in Flutter?

I am learning MVVM with the Provider package in Flutter.
I am creating a screen using StatelessWidget.
I have come to the point where I need to cache the widget, where should I store it?
StatelessWidget is immutable, so it cannot have variables in its properties.
MVVM's ViewModel cannot depend on a View, so it cannot have a Widget instance.
In the above question, I used Widget as an example, but there are other things I would like to store in View variables, such as GlobalKey.
The test code is as follows The purpose is to reference _needCacheWidget and _key later, but
I get a warning in the comment section
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
runApp(MultiProvider(
providers: [
ChangeNotifierProvider<TestViewModel>(create: (context) => TestViewModel()),
],
child: S1(),
));
}
class TestViewModel extends ChangeNotifier {
var _count = 0;
int get count {
return _count;
}
void increment() {
this._count += 1;
notifyListeners();
}
}
/// This class (or a class that this class inherits from) is marked as '#immutable',
/// but one or more of its instance fields aren't final: S1._needCacheWidget, S1._key (Documentation)
class S1 extends StatelessWidget {
Widget? _needCacheWidget = null;
GlobalKey? _key = null;
Widget _getNeedCacheWidget() {
GlobalKey key = _key ?? GlobalKey();
return _needCacheWidget ?? Text("need cache widget", key: key);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("text"),
),
body: Column(
children: [
Consumer<TestViewModel>(
builder: (context, testViewModel, child) {
return Text("count-${testViewModel.count}");
},
),
_getNeedCacheWidget(),
],
),
floatingActionButton: Consumer<TestViewModel>(
builder: (context, testViewModel, child) {
return FloatingActionButton(
onPressed: () {
testViewModel.increment();
print("globalKey is $_key"); // I want to refer to GlobalKey of NeedCacheWidget here.
},
child: const Icon(Icons.add),
);
},
),
),
);
}
}

Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder

I am using a Selector which rebuilds when a data in Bloc changes. Which woks fine but when the data changes it reloads the whole tree not just the builder inside Selector.
In my case the selector is inside a StreamBuilder. I need this because the stream is connected to API. So inside the stream I am building some widget and One of them is Selector. Selector rebuilds widgets which is depended on the data from the Stream.
Here is My Code. I dont want the Stream to be called again and again. Also the Stream gets called because the build gets called every time selector widget rebuilds.
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/data_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiProvider(providers: [
ChangeNotifierProvider<DataBloc>(
create: (_) => DataBloc(),
)
], child: ProviderTest()),
);
}
}
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
StreamBuilder(
stream: Provider.of<DataBloc>(context).getString(),
builder: (_, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + snapshot.data),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}),
],
);
}
return Container();
},
)
],
),
);
}
}
bloc.dart
import 'package:flutter/foundation.dart';
class DataBloc with ChangeNotifier {
int _willChange = 0;
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
Also if I remove the StreamBuilder then the Selector acts like its suppose to. Why does StreamBuilder Rebuilds in this case? Is there anyway to prevent this?
Based on the code that you've shared, you can create a listener to your Stream on your initState that updates a variable that keeps the most recent version of your data, and then use that variable to populate your widgets. This way the Stream will only be subscribed to the first time the Widget loads, and not on rebuilds. I can't test it directly as I don't have your project. But please try it out.
Code example based on your code
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
String _snapshotData;
#override
void initState() {
listenToGetString();
super.initState();
}
void listenToGetString(){
Provider.of<DataBloc>(context).getString().listen((snapshot){
setState(() {
_snapshotData = snapshot.data;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + _snapshotData),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}
),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}
),
],
)
],
),
);
}
}
I found the problem after reading this blog post here. I lacked the knowlwdge on how the Provider lib works and how its doing all the magic stuff out of Inherited widgets
The point and quote that solves this problem is. ( A quation from the blog post above)
When a Widget registers itself as a dependency of the Provider’s
InheritedWidget, that widget will be rebuilt each time a variation in
the “provided data” occurs (more precisely when the notifyListeners()
is called or when a StreamProvider’s stream emits new data or when a
FutureProvider’s future completes).
That means the variable that i am changing and the Stream that i am listning to, exists in the Same Bloc! that was the mistake. So when I change the val and call notifyListener() in a single bloc, all things reloads which is the default behaviour.
All I had to do to solve this problem is to make another Bloc and Abstract the Stream to that particular bloc(I think its a Good Practice also). Now the notifyListener() has no effect on the Stream.
data_bloc.dart
class DataBloc with ChangeNotifier {
int _willChange = 0;
String data = "";
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Future<String> getData () async {
return "Data";
}
}
stream_bloc.dart
import 'package:flutter/foundation.dart';
class StreamBloc with ChangeNotifier {
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
And the problem is solved. Now the Stream will only be called if its invoked but not when the variable changes in the data_bloc

StreamBuilder not re-rendering the widget inside?

I created this code, what i want to happen is when i press on the button i want the piechart to re-render with the new values (which should be old values but the food value increased by 1)
I am using a piechart from pie_chart: 0.8.0 package.
Deposit is nothing but a pojo (String category and int deposit)
the bloc.dart contains a global instance of the bloc, a getter for the stream and initialization of a stream of type
Here's my code:
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'bloc.dart';
import 'Deposit.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'bloc Chart',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Map<String, double> datamap = new Map();
#override
Widget build(BuildContext context) {
datamap.putIfAbsent("Food", () => 5);
datamap.putIfAbsent("transportation", () => 3);
return Scaffold(
appBar: AppBar(
title: Text("PieChart using blocs"),
),
body: Column(
children: <Widget>[
StreamBuilder<Deposit>(
stream: bloc.data, //A stream of Deposit data
builder: (context, snapshot) {
addDeposit(Deposit("Food", 1), datamap);
debugPrint("Value of food in map is: ${datamap["Food"]}");
return PieChart(dataMap: datamap);
}),
SizedBox.fromSize(
size: Size(20, 10),
),
RaisedButton(
onPressed: () {
bloc.add(Deposit("Food", 1)); //returns the stream.add
},
child: Icon(Icons.add),
),
],
),
);
}
void addDeposit(Deposit dep, Map<String, double> map) {
if (map.containsKey(dep.category)) {
map.update(dep.category, (value) => value + dep.price);
} else
map.putIfAbsent(dep.category, () => dep.price);
}
}
I think your problem is that the stream doesn't trigger new events. You don't have to close the stream to rebuild. I can't see anywhere in your code where you are triggering new events for the stream. Check below code to see a simple way how you can update a StatelessWidget using a StreamBuilder.
class CustomWidgetWithStream extends StatelessWidget {
final CustomBlock block = CustomBlock();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder(
stream: block.stream,
builder: (context, stream) {
return Text("${stream.data.toString()}");
}),
RaisedButton(
onPressed: () {
block.incrementNumber();
},
child: Text("Increment"),
)
],
);
}
}
class CustomBlock {
num counter = 10;
final StreamController<num> _controller = StreamController();
Stream<num> get stream => _controller.stream;
CustomBlock() {
_controller.onListen = () {
_controller.add(counter); // triggered when the first subscriber is added
};
}
void incrementNumber() {
counter += 1;
_controller.add(counter); // ADD NEW EVENT TO THE STREAM
}
dispose() {
_controller.close();
}
}
Although this is a working code snippet, I would strongly suggest to change your widget from StatelessWidget to StatefulWidget, for two reasons:
* if you go "by the book", if a widget changes the content by itself, then it's not a StatelessWidget, a stateless widget only displays data that is given to it. In your case, the widget is handling the tap and then decides what to do next and how to update itself.
* if you are using streams, in a stateful widget you can safely close the stream, as you can see in the above code, there's no safe way to close the stream. If you don't close the stream, there might be unwanted behaviour or even crashes.
This is my bloc file
import 'package:rxdart/rxdart.dart';
import 'package:testing/Deposit.dart';
class Bloc{
final _data = new BehaviorSubject<Deposit>();
Stream<Deposit> get data => _data.stream;
Function(Deposit) get add => _data.sink.add;
void dispose(){
_data.close();
}
}
Bloc bloc = new Bloc();

How to keep state using flutter page route

I have a simple app, it has named route in an appbar to a stateful widget. Currently, whenever I navigate away from the stateful widget and back again, the stateful widget loses its state (in this case my counter goes back to 0). I'd like the stateful widget to keep its state, but I'm not sure what I'm doing wrong. Do I need to explicitly keep state around? How? Should I be using didUpdateWidget to transfer state? Or something else?
This is my main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import './pages/home_page.dart';
import './pages/counter_page.dart';
void main()
{
CounterPage counterPage = new CounterPage();
return runApp(new MaterialApp(
home: new HomePage(),
theme: new ThemeData.dark(),
routes: <String, WidgetBuilder> {
"main": (BuildContext context) => new HomePage(),
'counter_page': (BuildContext context) {
return counterPage;
}
},
));
}
This is the counter_page.dart
import 'dart:async';
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
static String route = "counter_page";
CounterPage();
#override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter;
#override
void initState() {
super.initState();
_counter = 0;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Count"),
),
body: new Text("$_counter"),
floatingActionButton: new FloatingActionButton(
onPressed: () => setState(() {_counter += 1;}),
tooltip: 'Count',
child: new Icon(Icons.refresh),
),
);
}
}
There are multiple ways to handle state management in Flutter, but one way of doing this is by using the provider package. With this, even if the Screen gets rebuilt multiple times, state management won't be affected.