Update the display on a regular basis in flutter - dart

In flutter, we want to update the display on a regular basis.
We can update the display when the value changes.
However, the set value does not change in this case.
Therefore, we do not know what to do as a trigger to update the display.
We changed from Stateless Widget to Stateful Widget. And we started the timer in initState() and canceled the timer in dispose(). We regularly call notifyListeners () on ScopedModel and are updating the drawing.
It works as expected. Unfortunately, it is not a beautiful way. It is hard to understand, it is annoying. Do not you know a better way? How should we do?
We will clarify our implementation example below. It is a minimal code.
xxxxx_widget.dart
import 'package:flutter/material.dart';
import 'package:pregnancy/scoped_model/xxxxx_model.dart';
import 'package:pregnancy/widgets/yyyyy_widget.dart';
import 'package:scoped_model/scoped_model.dart';
class XxxxxWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<XxxxxModel>(
model: XxxxxModel(),
child: YyyyyWidget(),
);
}
}
yyyyy_widget.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:pregnancy/scoped_model/xxxxx_model.dart';
import 'package:scoped_model/scoped_model.dart';
class YyyyyWidget extends StatefulWidget {
#override
YyyyyWidgetState createState() {
return new YyyyyWidgetState();
}
}
class YyyyyWidgetState extends State<YyyyyWidget> {
Timer _timer;
#override
void initState() {
_timer = Timer.periodic(
const Duration(milliseconds: 500),
(Timer t) {
XxxxxModel.of(context).notify();
},
);
super.initState();
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<XxxxxModel>(
builder: (context, child, model) {
var diff = model.datetime.difference(DateTime.now());
var hours = diff.inHours.remainder(Duration.hoursPerDay);
var minutes = diff.inMinutes.remainder(Duration.minutesPerHour);
var seconds = diff.inSeconds.remainder(Duration.secondsPerMinute);
return Text('${hours} hours ${minutes} minutes ${seconds} seconds');
},
);
}
}
xxxxx_model.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class XxxxxModel extends Model {
static XxxxxModel of(BuildContext context) =>
ScopedModel.of<XxxxxModel>(context);
DateTime _datetime = DateTime.now().add(Duration(days: 1));
get datetime => _datetime;
set datetime(DateTime value) {
_datetime = value;
notifyListeners();
}
void notify() {
notifyListeners();
}
}

You could use a StreamBuilder that listens to an interval stream. It automatically manages the subscription.
Your model can provide the stream as a property.

Related

How to open Scaffold's Drawer on page load?

Logging into our Flutter app opens to dashboard that has a Scaffold with a Drawer full of menu items.
I'd like to perform some A/B testing with having the Drawer open on page load or at least animating the Drawer being opened immediately on load.
I'm aware of Scaffold.of(context).openDrawer() but I'm not sure where to place this code so that it will run immediately after the build() method. I'm also not aware of any fields on either Drawer or Scaffold which would load with the Drawer open.
Thanks for your time and help.
You need to wait after the first frame is loaded.
_onLayoutDone(_) {
//your logic here
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
I wrote a post about this, you can take a look if you want : https://medium.com/#diegoveloper/flutter-widget-size-and-position-b0a9ffed9407
Override initState.
#override
void initState() {
super.initState();
// use this
Timer.run(() => Scaffold.of(context).openDrawer());
}
Store a state variable to hide and show drawer - isDrawerBeingShown.
Based on the state variable toggle the state of drawer. It is set to false by default so it will be displayed for the first time.
void _showDrawer(BuildContext context) async it must be marked as async so that it runs after build method.
Create showDrawerUtility method to show drawer on demand when ever required.
Edit:
Use GlobalKey
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class MainScreen extends StatefulWidget {
MainScreen({Key key }) : super(key: key);
#override
State<MainScreen> createState() => new MainScreenState();
}
class MainScreenState extends State<MainScreen> {
bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if(!isDrawerBeingShown) {
_scaffoldKey.currentState.openDrawer();
setState(() => isDrawerBeingShown = true);
}
}
#override
Widget build(BuildContext context) { // build method goes here}
}
follow my code
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class openDrawerOnLoadPage extends StatefulWidget {
openDrawerOnLoadPage({Key? key}) : super(key: key);
#override
_openDrawerOnLoadPageState createState() => _openDrawerOnLoadPageState();
}
class _openDrawerOnLoadPageState extends State<openDrawerOnLoadPage> {
late bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if (!isDrawerBeingShown) {
EasyDebounce.debounce('openDrawer', Duration(milliseconds: 100),
() async {
_scaffoldKey.currentState!.openDrawer();
setState(() => isDrawerBeingShown = true);
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
);
}
}

How to make a Sink<Locale> to format the result of a Stream<String>?

In google IO 18, the Flutter presenters have showed a feature but have not showed how to implement this.
The video (at exact time) is: https://youtu.be/RS36gBEp8OI?t=1776
How to implement such thing? How can I properly make the Stream to be correctly formatted based on a Sink?
(sorry but I am not too familiar with Rx)
Use the combineLatest function from the rxdart package. It takes the latest values of input streams, so any time either the locale or cart items change it will calculate and format the total cost.
import 'dart:async'; // Sink, Stream
import 'dart:ui'; // Locale
import 'package:rxdart/rxdart.dart'; // Observable, *Subject
class Bloc {
var _locale = BehaviorSubject<Locale>(seedValue: Locale('en', 'US'));
var _items = BehaviorSubject<List<CartItem>>(seedValue: []);
Stream<String> _totalCost;
Sink<Locale> get locale => _locale.sink;
Stream<List<CartItem>> get items => _items.stream;
Stream<String> get totalCost => _totalCost;
Bloc() {
_totalCost = Observable.combineLatest2<Locale, List<CartItem>, String>(
_locale, _items, (locale, items) {
// TODO calculate total price of items and format based on locale
return 'USD 10.00';
}).asBroadcastStream();
}
void dispose() {
_locale.close();
_items.close();
}
}
Disclaimer: I didn't try to run this code so there might be errors but the basic idea should be solid.
The best candidate for doing this cross-platform is NumberFormat from the intl package. However you still have to pass it a locale string ("en_US") and ISO 4217 currency code ("USD").
After a little digging I couldn't find this information in any Dart package. The NumberFormat class has a private map for looking up a currency symbol ("$") from a currency code, but keys of the map, the currency codes, are inaccessible. So I decided to make a package that makes locale strings and currency codes available.
currency_bloc.dart
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'package:intl/intl.dart';
import 'package:locales/locales.dart';
import 'package:locales/currency_codes.dart';
class LocalCurrency {
const LocalCurrency(this.locale, this.code);
final Locale locale;
final CurrencyCode code;
#override toString() => '$code ($locale)';
#override operator==(o) => o is LocalCurrency && o.locale == locale && o.code == code;
#override hashCode => toString().hashCode;
}
/// Emits currency strings according to a locale.
class CurrencyBloc {
// Inputs.
final _valueController = StreamController<double>();
final _currencyController = StreamController<LocalCurrency>();
// Outputs.
final _currency = BehaviorSubject<String>();
/// The last formatted currency value emitted from the output stream.
String lastCurrency;
// For synchronously receiving the latest inputs.
double _value;
NumberFormat _formatter;
CurrencyBloc({LocalCurrency initialCurrency, double initialValue}) {
_valueController.stream
.distinct()
.listen((value) => _updateCurrency(value: value));
_currencyController.stream
.distinct()
.listen((currency) => _updateCurrency(currency: currency));
// Initialize inputs.
locale.add(initialCurrency ??
LocalCurrency(Locale.en_US, CurrencyCode.usd));
value.add(initialValue ?? 0.0);
}
void dispose() {
_valueController.close();
_currencyController.close();
_currency.close();
}
_updateCurrency({double value, LocalCurrency currency}) {
if (currency != null) {
_formatter = NumberFormat.simpleCurrency(
locale: '${currency.locale}',
name: '${currency.code}',
decimalDigits: 2);
}
if (value != null) {
_value = value;
}
if (_value != null && _formatter != null) {
lastCurrency = _formatter.format(_value);
_currency.add(lastCurrency);
}
}
/// Change the current [Locale] and/or [CurrencyCode].
Sink<LocalCurrency> get locale => _currencyController.sink;
/// Change the the value to be formatted.
Sink<double> get value => _valueController.sink;
/// Formatted currency.
Stream<String> get currency => _currency.stream;
}
currency_provider.dart (conventional)
class CurrencyProvider extends InheritedWidget {
CurrencyProvider({Key key, #required this.bloc, #required Widget child})
: super(key: key, child: child);
final CurrencyBloc bloc;
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static CurrencyBloc of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(CurrencyProvider) as CurrencyProvider)
.bloc;
}
Example usage
...
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CurrencyBloc bloc;
#override
Widget build(BuildContext context) =>
CurrencyProvider(bloc: bloc, child: CurrencyExample());
#override
void initState() {
super.initState();
bloc = CurrencyBloc();
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
void didUpdateWidget(StatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget);
bloc.dispose();
bloc = CurrencyBloc();
}
}
class CurrencyExample extends StatelessWidget {
final controller = TextEditingController();
#override
Widget build(BuildContext context) {
final bloc = CurrencyProvider.of(context);
return ListView(
children: <Widget>[
TextField(controller: controller),
StreamBuilder(
stream: bloc.currency,
initialData: bloc.lastCurrency,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
return new Text('${snapshot.error}');
}
return Center(child: CircularProgressIndicator());
}),
FlatButton(
child: Text('Format Currency'),
onPressed: () => bloc.value.add(double.tryParse(controller.text)),
)
],
);
}
}

How to change background color on time with Flutterr?

I have been completely unable to figure this out. After a good deal of research and screwing around I can't see a way to link a Timer.periodic to the setState of the state of a stateful widget. I'm trying to change the background color of my app each frame (or just every 17ms, close enough) to have a gradual change, by incrementing through an array of color values that I've defined. I just don't know how to do the actual changing based on the timer and I'm totally lost at this point.
Edit: Here is the full code of the app so far.
import 'package:flutter/material.dart';
import 'colors.dart';
import 'dart:math';
import 'dart:async';
final Random randProgress = new Random();
final rgb_color_container rgbColorContainer = new rgb_color_container();
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MainApp();
}
}
class MainApp extends StatefulWidget {
#override
MainAppState createState() => new MainAppState();
}
class MainAppState extends State<MainApp> {
int colorSpeed = 1;
int colorProgress = randProgress.nextInt(1530);
#override
Widget build(BuildContext context){
return new MaterialApp(
home: new Scaffold(
),
theme: ThemeData(
canvasColor: rgbColorContainer.light[colorProgress],
primaryColor: rgbColorContainer.dark[colorProgress],
accentColor: rgbColorContainer.dark[colorProgress],
),
);
}
}
Here are the basic steps:
Override the setState() inside MainAppState or according to your needs you can override initState().
Inside it add a Timer.periodic
assign a value to colorProgress by changing colorProgress with
timer.
Like below(not checked, only the idea):
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
setState(() {
//change your colorProgress here
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
These may help:
How to create Timer.periodic.
Change color.
setState not update.

Remote Config Device Language Changes in Flutter

I am encountering a problem, where localization works fine, but the applications needs to be restarted in order for the changes to propagate.
Orientation changes
I know about OrientationBuilder, which will call its builder whenever it detects a change in the device's orientation, which in e.g. Android would be considered as a configuration change, just like device language changes.
Language changes
Is there something like LanguageBuilder? I could not find anything on my own and not on flutter.io nor on pub. I have read this tutorial and know about Locale, but I do not see a Stream for Locale.
My problem is that changing the language in iOS and Android native is really smooth. It gets handled automatically and perfectly integrates with services like Firebase Remote Config.
I really wonder if there is some method that will allow me to refresh my localization.
Question
So I am asking how I can refresh my Remote Config when the device language changes.
No there's no Builder for Locale.
Instead, there's an InheritedWidget which you can subscribe to using Localizations.of.
Since it is an InheritedWidget, all widgets that call Localizations.of will automatically refresh on locale change.
EDIT :
A example on how to live reload text using Flutter Locale system :
Let's assume you have the following class that holds translations :
class MyData {
String title;
MyData({this.title});
}
You'd then have a LocalizationsDelegate that contains such data. A dumb implementation would be the following :
class MyLocale extends LocalizationsDelegate<MyData> {
MyData data;
MyLocale(this.data);
#override
bool isSupported(Locale locale) {
return true;
}
#override
Future<MyData> load(Locale locale) async {
return data;
}
#override
bool shouldReload(MyLocale old) {
return old.data != data;
}
}
To use it simply pass it to MaterialApp.localizationsDelegates (be sure to add flutter_localizations to your pubspec.yaml) :
LocalizationsDelegate myLocale = MyLocale(MyData(title: "Foo"));
...
MaterialApp(
localizationsDelegates: [
myLocale,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
);
You can then freely live reload your translations by replacing myLocale with a new MyLocale instance.
Here's a full example of a click counter app. But where the current count is instead stored inside Locale (because why not ?)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class MyCount {
String count;
MyCount({this.count});
}
class MyCountLocale extends LocalizationsDelegate<MyCount> {
MyCount data;
MyCountLocale(this.data);
#override
bool isSupported(Locale locale) {
return true;
}
#override
Future<MyCount> load(Locale locale) async {
return data;
}
#override
bool shouldReload(MyCountLocale old) {
return old.data != data;
}
}
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ValueNotifier<int> count = ValueNotifier<int>(0);
LocalizationsDelegate myLocale;
#override
void initState() {
count.addListener(() {
setState(() {
myLocale = MyCountLocale(MyCount(count: count.value.toString()));
});
});
myLocale = MyCountLocale(MyCount(count: count.value.toString()));
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
myLocale,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
home: MyHomePage(count: count),
);
}
}
class MyHomePage extends StatefulWidget {
final ValueNotifier<int> count;
MyHomePage({this.count});
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
primary: true,
appBar: AppBar(),
body: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => widget.count.value++,
child: Icon(Icons.plus_one),
),
ListTile(
title: Text(Localizations.of<MyCount>(context, MyCount).count),
),
],
),
);
}
}
Device language changes can be detected using a WidgetsBindingObserver.
It is the simplest to use it with a StatefulWidget in your State (with WidgetsBindingObserver):
class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
#override
void didChangeLocales(List<Locale> locale) {
// The device language was changed when this is called.
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
...
}
This means that you can now reload your RemoteConfig in didChangeLocales:
#override
void didChangeLocales(List<Locale> locale) {
_updateRemoteConfig();
}
Future<void> _updateRemoteConfig() async {
final remoteConfig = await RemoteConfig.instance;
await remoteConfig.activateFetched(); // This will apply the new locale.
}

setState doesn't update the user interface

I've been facing some problems related to the setState function while using Stateful Widgets that updates itself with the help of Timers. The code below show 2 main classes that replicate how I came to find this error. The Text Widget "Lorem" should be inserted within 10 seconds - and it is - but it's never shown. I tried to debug the array "Items" and it does contain the "lorem" Text Widget after 5 seconds, as it should. The "build" function runs but doesn't make any difference in the UI.
class textList extends StatefulWidget {
#override
State<StatefulWidget> createState() =>
new _textListState();
}
class _textListState extends State<textList>
with TickerProviderStateMixin {
List<Widget> items = new List();
Widget lorem = new textClass("Lorem");
Timer timer;
#override
void initState() {
super.initState();
items.add(new textClass("test"));
items.add(new textClass("test"));
timer = new Timer.periodic(new Duration(seconds: 5), (Timer timer) {
setState(() {
items.removeAt(0);
items.add(lorem);
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
Iterable<Widget> content = ListTile.divideTiles(
context: context, tiles: items).toList();
return new Column(
children: content,
);
}
}
class textClass extends StatefulWidget {
textClass(this.word);
final String word;
#override
State<StatefulWidget> createState() =>
new _textClass(word);
}
class _textClass extends State<textClass>
with TickerProviderStateMixin {
_textClass(this.word);
String word;
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
setState(() {
word += "t";
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
return new Text(word);
}
}
This is not how I came to find this error but this is the simplest way to replicate it. The main idea is: The children texts should keep updating themselves (in this case, adding "t"s in the end) and, after 5 seconds, the last of them should be replaced for the Text Widget "Lorem", what does happen in the list but not in the UI.
Here's what's wrong:
A State should never have any constructor arguments. Use the widget property to get access to final properties of the associated StatefulWidget.
Flutter is reusing your _textClass instance because the class name and keys match. This is a problem since you only set widget.word in initState so you're not picking up the new word configuration information. You can fix this either by giving the StatefulWidget instances unique keys to disambiguate them and cause the old State to be disposed, or you can keep around the old State and implement didUpdateWidget. The latter approach is shown below.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new Scaffold(
appBar: new AppBar(title: new Text('Example App')),
body: new textList(),
),
));
}
class textList extends StatefulWidget {
#override
State<StatefulWidget> createState() =>
new _textListState();
}
class _textListState extends State<textList>
with TickerProviderStateMixin {
List<Widget> items = new List();
Widget lorem = new textClass("Lorem");
Timer timer;
#override
void initState() {
super.initState();
items.add(new textClass("test"));
items.add(new textClass("test"));
timer = new Timer.periodic(new Duration(seconds: 5), (Timer timer) {
setState(() {
items.removeAt(0);
items.add(lorem);
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
Iterable<Widget> content = ListTile.divideTiles(
context: context, tiles: items).toList();
return new Column(
children: content,
);
}
}
class textClass extends StatefulWidget {
textClass(this.word);
final String word;
#override
State<StatefulWidget> createState() =>
new _textClass();
}
class _textClass extends State<textClass>
with TickerProviderStateMixin {
_textClass();
String word;
Timer timer;
#override
void didUpdateWidget(textClass oldWidget) {
if (oldWidget.word != widget.word) {
word = widget.word;
}
super.didUpdateWidget(oldWidget);
}
#override
void initState() {
super.initState();
word = widget.word;
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
setState(() {
word += "t";
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
return new Text(word);
}
}

Resources