Best place to do a check for auth session is still alive in flutter - flutter

I am building an app which is using native (iOS and Android) framework to do an authentication. I created a plugin which wraps all framework exposed methods to flutter. I have to check every screen change that user is authenticated and session is alive and if not call framework to do so, what will be the best place in flutter to do such check?

Inside a widget.
Widgets are nearly always where you want to put your logic.
To be exact, what you want is a StatefulWidget that expose an InheritedWidget using a custom static MyInheritedWidget of(BuildContext context) method. InheritedWidget which will possess all the login informations and a few "onChange" methods.
You will then be able to access anywhere inside your app to this InheritedWidget using MyAuth.of(context) and therefore have access to all the needed data.
Usually you'll end up with this :
/// The user data model. May be serializable. Usually immutable
#immutable
class MyUser {
const MyUser({this.name});
final String name;
}
/// The entry point of your global logic.
/// Most of the time, will only take one child as parameter
/// and nothing else. And will also expose a [of] method
class MyAuth extends StatefulWidget {
final Widget child;
const MyAuth({this.child});
/// Fetch the closest [_MyInheritedAuth] from the widget tree
/// using context. And bind the widget possessing that context
/// to reload evytimes the inherited widget changes
static _MyInheritedAuth of(BuildContext context) {
final _MyInheritedAuth inherited = context.inheritFromWidgetOfExactType(_MyInheritedAuth);
return inherited;
}
#override
_MyAuthState createState() => new _MyAuthState();
}
typedef void MyUserNameChange(String name);
/// Your controller. Will handle all the logic and just
/// pass it all to [_MyInheritedAuth]
class _MyAuthState extends State<MyAuth> {
MyUser user;
#override
initState() {
super.initState();
user = const MyUser(name: 'foo');
}
void onUserNameChange(String name) {
setState(() {
user = new MyUser(name: name);
});
}
#override
Widget build(BuildContext context) {
return new _MyInheritedAuth(user: user, editUserName: onUserNameChange, child: widget.child,);
}
}
/// What the other widgets will get when user [MyAuth.of].
/// Inherit from [InheritedWidget] to be able to bind a context
/// to this widget's updates/
class _MyInheritedAuth extends InheritedWidget {
final MyUser user;
final MyUserNameChange editUserName;
const _MyInheritedAuth({ #required this.user, #required this.editUserName, #required Widget child,}): super(child: child);
#override
bool updateShouldNotify(_MyInheritedAuth oldWidget) {
return user != oldWidget.user || editUserName != oldWidget.editUserName;
}
}
And whenever you need to use user infos, you can just do MyAuth.of(context).user.

Related

How should I implement the init method? In a stateful or stateless widget?

What is the rule of thumb to use an initial method for a widget. Shall I use the:
A. classical stateful widget approach?
Or is it better to stick with the B. stateless widget approach?
Both seem to work from my testing. In terms of code reduction, it seems the B. approach is better, shorter, cleaner, and more readable. How about the performance aspect? Anything else that I could be missing?
Initializing a controller should be a one-time operation; if you do it on a StatelessWidget's build method, it will be triggered every time this widget is rebuilt. If you do it on a StatefulWidget's initState, it will only be called once, when this object is inserted into the tree when the State is initialized.
I was looking for initializing some values based on values passed in constructor in Stateless Widget.
Because we all know for StatefulWidget we have initState() overridden callback to initialize certain values etc. But for Stateless Widget no option is given by default. If we do in build method, it will be called every time as the view update. So I am doing the below code. It works. Hope it will help someone.
import 'package:flutter/material.dart';
class Sample extends StatelessWidget {
final int number1;
final int number2;
factory Sample(int passNumber1, int passNumber2, Key key) {
int changeNumber2 = passNumber2 *
2; //any modification you need can be done, or else pass it as it is.
return Sample._(passNumber1, changeNumber2, key);
}
const Sample._(this.number1, this.number2, Key key) : super(key: key);
#override
Widget build(BuildContext context) {
return Text((number1 + number2).toString());
}
}
Everything either a function or something else in widget build will run whenever you do a hot reload or a page refreshes but with initState it will run once on start of the app or when you restart the app in your IDE for example in StatefulWidget widget you can use:
void initState() {
super.initState();
WidgetsBinding.instance!
.addPostFrameCallback((_) => your_function(context));
}
To use stateful functionalities such as initState(), dispose() you can use following code which will give you that freedom :)
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Function onDespose;
final Widget child;
const StatefulWrapper(
{super.key,
required this.onInit,
required this.onDespose,
required this.child});
#override
State<StatefulWrapper> createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
// ignore: unnecessary_null_comparison
if (widget.onInit != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
#override
void dispose() {
if (widget.onDespose != null) {
widget.onDespose();
}
super.dispose();
}
}
Using above code you can make Stateful Wrapper which contains stateful widget's method.
To use Stateful Wrapper in our widget tree you can just wrap your widget with Stateful Wrapper and provide the methods or action you want to perform on init and on dispose.
Code available on Github
NOTE: You can always add or remove method from Stateful Wrapper Class according to your need!!
Happy Fluttering!!

Is it possible to have both a stateful widgets and a stateless widget as subtypes of another class?

I recently tried to create an abstract widget, that has then both a stateless and a stateful implementation, which both can be accessed via factory-methods.
Below I added a minimal example of the only real working solution I have figured out that works for my use case, but it leaves me with some things to be desired.
For example with this solution, I have to declare and override every variable in the sub-classes, while I would really like to rely on the fact that they are subtypes and implicitly have those variables.
Has anyone of you ever needed to do a similar thing? Have you worked out a different approach?
For those concerned about as to why I would need this: I wanted to make a singular Button-Class for my App, that then has different implementations for specific styles of buttons (regular button, a 'striped' button, a button that 'loads' as the user scrolls down a page and becomes active once the user reached the end of the page, etc.). That way I could then simply call 'Button.implementation' wherever i needed a specific button, and have all the button-related Code in the same place.
Cheers.
abstract class A {
final int intellect;
A(this.intellect);
factory A.giveMeB(int intellect) {
return _B(intellect);
}
factory A.giveMeC(int intellect) {
return _C(intellect);
}
}
class _B extends StatelessWidget implements A {
#override
final int intellect;
_B(this.intellect);
#override
Widget build(BuildContext context) {
return SizedBox.shrink();
}
}
class _C extends StatefulWidget implements A {
#override
final int intellect;
const _C(this.intellect, {Key key}) : super(key: key);
#override
_CState createState() => _CState();
}
class _CState extends State<_C> {
#override
Widget build(BuildContext context) {
return Container();
}
}

How to pass an object from Stateful Widget to its State? (not in Widget)

I am trying to pass a User object from my stateful widget, to its state.
class NavBar extends StatefulWidget {
final User user;
NavBar({this.user});
#override
NavBarState createState() => NavBarState();
}
class NavBarState extends State<NavBar> {
int _currentIndex = 0;
final List<Widget> _children = [
CalendarWidget(),
HomeWidget(),
MessagingWidget(),
ProfilePage(user: user)
];
Widget build(BuildContext context) {
All other solutions say to use widget.user like ProfilePage(user: widget.user) but that throws error:
Only static members can be accessed in initializers.dart(implicit_this_reference_in_initializer)
How can I access User object in the state of NavBar so I can send it to ProfilePage()?
you can use widget.yourObjectsName to access StateFul widget's Object in its state
you can use any property of Stateful Widgets from its state by widget property of state which gives reference to state's Widget properties.

GlobalKey in InheritedWidget causing re-parent

Is passing a GlobalKey down the tree using an InheritedWidget an antipattern? The stateful widget using that key is re-created (i.e. a new state this initState/disposed) every time its subtree is re-built.
My InheritedWidget looks like:
import 'package:flutter/material.dart';
import '../widgets/carousel.dart';
import '../widgets/panel/panel.dart';
class _CarouselKey extends GlobalObjectKey<CarouselState> {
const _CarouselKey(Object value) : super(value);
}
class _ProgressiveChatHeaderKey extends GlobalObjectKey<PanelScaffoldState> {
const _ProgressiveChatHeaderKey(Object value) : super(value);
}
class DimensionScopedKeyProvider extends InheritedWidget {
final _CarouselKey parallelBubbleCarouselKey;
final _ProgressiveChatHeaderKey progressiveChatHeaderKey;
final String keyString;
DimensionScopedKeyProvider({
Key key,
#required this.keyString,
#required Widget child,
}) : parallelBubbleCarouselKey = _CarouselKey(keyString),
progressiveChatHeaderKey = _ProgressiveChatHeaderKey(keyString),
super(key: key, child: child);
static DimensionScopedKeyProvider of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(DimensionScopedKeyProvider)
as DimensionScopedKeyProvider);
}
#override
bool updateShouldNotify(DimensionScopedKeyProvider oldWidget) => oldWidget.keyString != keyString;
}
And this InheritedWidget is rendered with a constant keyString, meaning that 1) updateShouldNotify always returns false and 2) the hashCode of the GlobalKeys passed to my build methods via DimensionScopedKeyProvider.of() are always identical.
The stateful widget builds something like
GlobalKey<PanelScaffoldState> get _headerKey => //
DimensionScopedKeyProvider.of(context).progressiveChatHeaderKey;
// ...
PanelScaffold(
key: _headerKey,
// ...
)
When I change a property that affects the subtree that the PanelScaffold lives in, though, a new PanelScaffoldState is created and the old one is disposed, even though the widget tree hasn't changed structure and the _headerKey hasn't changed either.
I also able to solve this problem, but I have no idea why it works.
The solution is to cache the access to the GlobalKey in didChangeDependencies
#override
void didChangeDependencies() {
super.didChangeDependencies();
_headerKey ??= DimensionScopedKeyProvider.of(context).progressiveChatHeaderKey;
}
.... and now everything is working as expected again—the rebuilds re-parent the existing state.
Does anyone know why caching the getter to the GlobalKey is the key here?

Controlling State from outside of a StatefulWidget

I'm trying to understand the best practice for controlling a StatefulWidget's state outside of that Widgets State.
I have the following interface defined.
abstract class StartupView {
Stream<String> get onAppSelected;
set showActivity(bool activity);
set message(String message);
}
I would like to create a StatefulWidget StartupPage that implements this interface. I expect the Widget to do the following:
When a button is pressed it would send an event over the onAppSelected stream. A controller would listen to this event and perform some action ( DB call, service request, etc ).
The controller can call showActivity or set message to have the view show progress with a message.
Because a Stateful Widget does not expose its State as a property, I don't know the best approach for accessing and modifying the State's attributes.
The way I would expect to use this would be something like this:
Widget createStartupPage() {
var page = new StartupPage();
page.onAppSelected.listen((app) {
page.showActivity = true;
//Do some work
page.showActivity = false;
});
}
I've thought about instantiating the Widget by passing in the state I want it to return in createState() but that feels wrong.
Some background on why we have this approach: We currently have a Dart web application. For view-controller separation, testability, and forward-thinking towards Flutter, we decided that we would create an interface for every view in our application. This would allow a WebComponent or a Flutter Widget to implement this interface and leave all of the controller logic the same.
There are multiple ways to interact with other stateful widgets.
1. findAncestorStateOfType
The first and most straightforward is through context.findAncestorStateOfType method.
Usually wrapped in a static method of the Stateful subclass like this :
class MyState extends StatefulWidget {
static of(BuildContext context, {bool root = false}) => root
? context.findRootAncestorStateOfType<_MyStateState>()
: context.findAncestorStateOfType<_MyStateState>();
#override
_MyStateState createState() => _MyStateState();
}
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return Container();
}
}
This is how Navigator works for example.
Pro:
Easiest solution
Con:
Tempted to access State properties or manually call setState
Requires to expose State subclass
Don't use this method when you want to access a variable. As your widget may not reload when that variable change.
2. Listenable, Stream and/or InheritedWidget
Sometimes instead of a method, you may want to access some properties. The thing is, you most likely want your widgets to update whenever that value changes over time.
In this situation, dart offer Stream and Sink. And flutter adds on the top of it InheritedWidget and Listenable such as ValueNotifier. They all do relatively the same thing: subscribing to a value change event when coupled with a StreamBuilder/context.dependOnInheritedWidgetOfExactType/AnimatedBuilder.
This is the go-to solution when you want your State to expose some properties. I won't cover all the possibilities but here's a small example using InheritedWidget :
First, we have an InheritedWidget that expose a count :
class Count extends InheritedWidget {
static of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<Count>();
final int count;
Count({Key key, #required Widget child, #required this.count})
: assert(count != null),
super(key: key, child: child);
#override
bool updateShouldNotify(Count oldWidget) {
return this.count != oldWidget.count;
}
}
Then we have our State that instantiate this InheritedWidget
class _MyStateState extends State<MyState> {
int count = 0;
#override
Widget build(BuildContext context) {
return Count(
count: count,
child: Scaffold(
body: CountBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
count++;
});
},
),
),
);
}
}
Finally, we have our CountBody that fetch this exposed count
class CountBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(Count.of(context).count.toString()),
);
}
}
Pros:
More performant than findAncestorStateOfType
Stream alternative is dart only (works with web) and is strongly integrated in the language (keywords such as await for or async*)
Automic reload of the children when the value change
Cons:
More boilerplate
Stream can be complicated
3. Notifications
Instead of directly calling methods on State, you can send a Notification from your widget. And make State subscribe to these notifications.
An example of Notification would be :
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
To dispatch the notification simply call dispatch(context) on your notification instance and it will bubble up.
MyNotification(title: "Foo")..dispatch(context)
Note: you need put above line of code inside a class, otherwise no context, can NOT call notification.
Any given widget can listen to notifications dispatched by their children using NotificationListener<T> :
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: onTitlePush,
child: Container(),
);
}
bool onTitlePush(MyNotification notification) {
print("New item ${notification.title}");
// true meaning processed, no following notification bubbling.
return true;
}
}
An example would be Scrollable, which can dispatch ScrollNotification including start/end/overscroll. Then used by Scrollbar to know scroll information without having access to ScrollController
Pros:
Cool reactive API. We don't directly do stuff on State. It's State that subscribes to events triggered by its children
More than one widget can subscribe to that same notification
Prevents children from accessing unwanted State properties
Cons:
May not fit your use-case
Requires more boilerplate
You can expose the state's widget with a static method, a few of the flutter examples do it this way and I've started using it as well:
class StartupPage extends StatefulWidget {
static StartupPageState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<StartupPageState>());
#override
StartupPageState createState() => new StartupPageState();
}
class StartupPageState extends State<StartupPage> {
...
}
You can then access the state by calling StartupPage.of(context).doSomething();.
The caveat here is that you need to have a BuildContext with that page somewhere in its tree.
There is another common used approach to have access to State's properties/methods:
class StartupPage extends StatefulWidget {
StartupPage({Key key}) : super(key: key);
#override
StartupPageState createState() => StartupPageState();
}
// Make class public!
class StartupPageState extends State<StartupPage> {
int someStateProperty;
void someStateMethod() {}
}
// Somewhere where inside class where `StartupPage` will be used
final startupPageKey = GlobalKey<StartupPageState>();
// Somewhere where the `StartupPage` will be opened
final startupPage = StartupPage(key: startupPageKey);
Navigator.push(context, MaterialPageRoute(builder: (_) => startupPage);
// Somewhere where you need have access to state
startupPageKey.currentState.someStateProperty = 1;
startupPageKey.currentState.someStateMethod();
I do:
class StartupPage extends StatefulWidget {
StartupPageState state;
#override
StartupPageState createState() {
this.state = new StartupPageState();
return this.state;
}
}
class DetectedAnimationState extends State<DetectedAnimation> {
And outside just startupPage.state
While trying to solve a similar problem, I discovered that ancestorStateOfType() and TypeMatcher have been deprecated. Instead, one has to use findAncestorStateOfType(). However as per the documentation, "calling this method is relatively expensive". The documentation for the findAncestorStateOfType() method can be found here.
In any case, to use findAncestorStateOfType(), the following can be implemented (this is a modification of the correct answer using the findAncestorStateOfType() method):
class StartupPage extends StatefulWidget {
static _StartupPageState of(BuildContext context) => context.findAncestorStateOfType<_StartupPageState>();
#override
_StartupPageState createState() => new _StartupPageState();
}
class _StartupPageState extends State<StartupPage> {
...
}
The state can be accessed in the same way as described in the correct answer (using StartupPage.of(context).yourFunction()). I wanted to update the post with the new method.
You can use eventify
This library provide mechanism to register for event notifications with emitter
or publisher and get notified in the event of an event.
You can do something like:
// Import the library
import 'package:eventify/eventify.dart';
final EventEmitter emitter = new EventEmitter();
var controlNumber = 50;
List<Widget> buttonsGenerator() {
final List<Widget> buttons = new List<Widget>();
for (var i = 0; i < controlNumber; i++) {
widgets.add(new MaterialButton(
// Generate 10 Buttons afterwards
onPressed: () {
controlNumber = 10;
emitter.emit("updateButtonsList", null, "");
},
);
}
}
class AState extends State<ofYourWidget> {
#override
Widget build(BuildContext context) {
List<Widget> buttons_list = buttonsGenerator();
emitter.on('updateButtonsList', null, (event, event_context) {
setState(() {
buttons_list = buttonsGenerator();
});
});
}
...
}
I can't think of anything which can't be achieved by event driven programming. You are limitless!
"Freedom cannot be bestowed — it must be achieved."
- Elbert Hubbard
Have you considered lifting the state to the parent widget? It is a common, though less ideal than Redux, way to manage state in React as far as I know, and this repository shows how to apply the concept to a Flutter app.