Getx Flutter dependency injection through binding - flutter

I am trying to use binding class to inject dependecies into my flutters application but for some reason its not working as i expected
binding class
class LibraryHomeBinding extends Bindings {
#override
void dependencies() {
Get.put(LibraryHomeController(), tag: 'home');
}
}
controller class
class LibraryHomeController extends GetxController {
#override
void onInit() {
print('initilizing');
super.onInit();
}
#override
void onReady() {
print('Controller ready');
super.onReady();
}
#override
void onClose() {
print('Controller closing');
super.onClose();
}
}
home
class LibraryHome extends StatelessWidget {
LibraryHome({super.key, required this.title});
final String title;
final libraryHomeBinding = Get.find(tag: 'home');
...
}
main
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: lightTheme,
getPages: [
GetPage(
name: '/home',
page: () => LibraryHome(title: 'Library'),
binding: LibraryHomeBinding(),
)
],
initialRoute: '/home',
);
}
}
i am getting this error
""dynamic" not found. You need to call "Get.put(dynamic())" or "Get.lazyPut(()=>dynamic())""

on the binding class i think using extends is not quite right but use instead with/implements
class LibraryHomeBinding with Bindings {
#override
void dependencies() {
/// use lazy put
Get.lazyPut(LibraryHomeController(),fenix: true,tag: 'home');
}
}
then on the controller do it like this
class LibraryHomeController extends GetxController {
final findBind = Get.find<LibraryHomeController>( tag: "home");
#override
void onInit() {
print('initilizing');
super.onInit();
}
}

when calling the Get.find() you specified the tag, but you didn't specify the type, Getx use the type and tag to generate a consistent key to get what you want so you need to do this :
final libraryHomeBinding = Get.find<LibraryHomeController>(tag: 'home');
or
final LibraryHomeController libraryHomeBinding = Get.find(tag: 'home');
now it will find it.

Related

Navigate from notification via beamer

I want to navigate to a specific page via beamer from a notification click.
In my main.dart I initialze my app and fcm. The class 'PushNotificationReceiver' should handle the notification logic.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PushNotificationReceiver.instance.initialize();
runApp(MultiProvider(providers: [
// Some of my providers
], builder: (context, _) => MyApp()));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
PushNotificationReceiver.instance.registerNotifications((route) => {
context.beamToNamed(route)
});
}
#override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {
return MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(delegate: _beamerDelegate),
);
}
}
}
I implemented the functions to receive and show local notifications but to simplify it I only paste the code for the click (removed null checks as well).
class PushNotificationReceiver {
static PushNotificationReceiver _instance;
void Function(String route) navigateFunction;
static PushNotificationReceiver get instance {
if (_instance == null) {
_instance = new PushNotificationReceiver();
}
return _instance;
}
Future<void> initialize() async {
await Firebase.initializeApp();
}
void registerNotifications(void Function(String route) navigateFunction) {
this.navigateFunction = navigateFunction;
// Called the other functions to receive notifications, but excluded them for simplicity.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
this.navigateFunction("/MyPage/${message.data["id"]}");
});
}
}
When I click on the notification I get the following error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: 'package:beamer/src/beamer.dart': Failed assertion: line 40 pos 14: 'BeamerProvider.of(context) != null': There was no Router nor BeamerProvider in current context. If using MaterialApp.builder, wrap the MaterialApp.router in BeamerProvider to which you pass the same routerDelegate as to MaterialApp.router.
I tried it first without a function that I pass in and a GlobalKey in the main.dart with the same result.
Any suggestions?
Found the solution.
My first approach of a global key works if I wrap my MaterialApp.router in a Beamerprovider (like the error message suggested).
final GlobalKey myGlobalKey = GlobalKey();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PushNotificationReceiver.instance.initialize();
runApp(MultiProvider(providers: [
// Some of my providers
], builder: (context, _) => MyApp()));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
PushNotificationReceiver.instance.registerNotifications();
}
#override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {
return BeamerProvider(
key: myGlobalKey,
routerDelegate: _beamerDelegate,
child: MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(
delegate: _beamerDelegate
)
)
);
}
}
}
That leads to my push notification receiver:
class PushNotificationReceiver {
static PushNotificationReceiver _instance;
static PushNotificationReceiver get instance {
if (_instance == null) {
_instance = new PushNotificationReceiver();
}
return _instance;
}
Future<void> initialize() async {
await Firebase.initializeApp();
}
void registerNotifications(void Function() {
// Called the other functions to receive notifications, but excluded them for simplicity.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
myGlobalKey.currentContext.beamToNamed("/MyPage/${message.data["id"]}");
});
}
}
I hope this will help some others too.

Flutter bloc state is not emitting or updating. Method mapEventToState is never called

The following code was working before null safety with flutter_bloc 4.0.1 but after null safety migration the state is not updating / emitting / broadcasting as expected with flutter_bloc 7.3.3.
The below _reactToState and mapEventToState methods are never called. How can I fix it?
Splash Screen
class SplashScreen extends StatefulWidget {
final Strapper strapper;
final Service? service;
SplashScreen(this.strapper, this.service);
#override
State<StatefulWidget> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc? _splashBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_splashBloc == null) {
_splashBloc = SplashBloc(widget.strapper, widget.service);
_splashBloc!.stream.listen(_reactToState);
}
}
#override
dispose() {
_splashBloc?.close();
_splashBloc = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider<SplashBloc>(
create: (context) => _splashBloc!,
child: BlocBuilder<SplashBloc, SplashBlocState>(
builder: (context, state) => Container(
child: Stack(
children: <Widget>[
LogoPanel(
_showWidgetForState(state),
),
],
),
),
),
);
}
void _reactToState(SplashBlocState state) {
if (state is InitializingSplashBlocState) {
if (widget.logOut) {
_splashBloc!.add(LogoutSplashBlocEvent());
} else {
_splashBloc!.add(CInitializationSplashBlocEvent());
}
} else if (state is AuthSuccessSplashBlocState) {
App.navigateToSomewhere(context, state.isNewUser);
}
}
Widget _showWidgetForState(SplashBlocState state) {
if (state is InitializingSplashBlocState) {
return _getProgressIndicator();
} else if (state is ChooseSomethingSplashBlockState ) {
return _showSignInWidget();
}
}
}
Splash Bloc
class SplashBloc extends Bloc<SplashBlocEvent, SplashBlocState> {
final Strapper? strapper;
final Service? service;
SplashBloc(this.strapper, this.service) : super(InitializingSplashBlocState());
#override
Stream<SplashBlocState> mapEventToState(event) async* {
if (event is CInitializationSplashBlocEvent) {
await strapper!.run();
}
bool chooseSomething = !service!.hasSomeSelection;
if (chooseSomething) {
yield ChooseSomethingSplashBlockState();
} else if (event is RAuthSplashBlocEvent) {
yield AuthSplashBlocState();
var authState = await _run();
yield authState;
}
}
Future<SplashBlocState> _run() async {
// Do something
}
}
Splash Bloc Event
abstract class SplashBlocEvent extends Equatable {
const SplashBlocEvent();
#override
List<Object> get props => [];
}
class CInitializationSplashBlocEvent extends SplashBlocEvent {}
class RAuthSplashBlocEvent extends SplashBlocEvent {}
Splash Bloc State
abstract class SplashBlocState extends Equatable {
const SplashBlocState();
#override
List<Object> get props => [];
}
class InitializingSplashBlocState extends SplashBlocState {}
class AuthSplashBlocState extends SplashBlocState {}
class ChooseSomethingSplashBlockState extends SplashBlocState {}
class AuthSuccessSplashBlocState extends SplashBlocState {
final CurrentUser? user;
final bool isNewUser;
AuthSuccessSplashBlocState(this.user, this.isNewUser);
}
As per the documentation:
In v6.0.0, the above snippet does not output the initial state and only outputs subsequent state changes. The previous behavior can be achieved with the following:
final bloc = MyBloc();
print(bloc.state);
bloc.listen(print);
So I changed my code in the Splash screen as following:
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_splashBloc == null) {
_splashBloc = SplashBloc(widget.strapper, widget.service);
_reactToState(_splashBloc!.state); // Added this line
_splashBloc!.stream.listen(_reactToState);
}
}
And that's it. It worked!
_reactToState and mapEventToState are definitely being called.
when you use Streamcontrollers it greatly simplifies state. I build a bloc code to manage state. The materialapp child is the splashWidget whose job is to render the hour, minute, second from bloc code emitting Time state. If the user clicks the splash screen or 5 seconds elapses the splash screen will be replaced with the HomePageWidget. bloc code controls the starting and stopping of the timer using an timerState event.
'package:flutter/material.dart';
import 'bloc_splash.dart';
import 'main.dart';
class SplashWidget extends StatelessWidget {
const SplashWidget({Key? key}) : super(key: key);
_redirectToHome(BuildContext context)
{
Navigator.pushReplacement(context,MaterialPageRoute(builder:(_)=>MyHomePage(title:"helloWorld")));
}
String _displayClock(Time ? data)
{
String retVal="";
if (data!=null)
{
retVal="Time: ${data.hour} : ${data.minute} : ${data.second}";
}
return retVal;
}
#override
Widget build(BuildContext context) {
SplashBloc _bloc=SplashBloc();
_bloc.timerOnChange(StartTimer());
return Scaffold(
body:InkWell(
onTap: (){_bloc.timerOnChange(StopTimer());
_redirectToHome(context);
},
child:Container(
child:
StreamBuilder<TimeState>(
stream:_bloc.timeStream,
builder:(context,snapshot)
{
if(snapshot.hasData && (snapshot.data is RedirectState))
{
return MyHomePage(title:"helloWorld");
}
return Center(child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Splash Screen", style:TextStyle(fontSize: 24,fontWeight: FontWeight.bold)),
Text(_displayClock(snapshot.data?.time)),
]));
}
)
))
);
}
}
bloc code
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:ui';
import 'dart:async';
abstract class TimerEvent extends Equatable{
const TimerEvent();
#override
List<Object>get props=>[];
}
class StartTimer extends TimerEvent{
const StartTimer();
}
class StopTimer extends TimerEvent{
const StopTimer();
}
class Time{
final int hour;
final int minute;
final int second;
Time(this.hour,this.minute,this.second);
}
class TimeState extends Equatable{
final Time time;
TimeState(this.time);
#override
List<Object> get props=>[time];
}
class RedirectState implements TimeState{
final Time time;
RedirectState(this.time);
#override
List<Object> get props=>[time];
#override
// TODO: implement stringify
bool? get stringify => throw UnimplementedError();
}
class TimerState extends Equatable{
final bool started;
const TimerState(this.started);
#override
List<Object> get props => [started];
}
class SplashBloc
{
SplashBloc();
Timer ?_timer;
var countDown=5;
Stream<TimeState> get timeStream=> _timeController.stream;
final _timeController =BehaviorSubject<TimeState>();
void dispose()
{
_timeController.close();
}
void _pushTimeOnTheStream(Timer timer)
{
DateTime now=DateTime.now();
_timeController.sink.add(TimeState(Time(now.hour,now.minute,now.second)));
this.countDown-=1;
if (this.countDown==0)
{
timerOnChange(StopTimer());
_timeController.sink.add(RedirectState(Time(0,0,0)));
}
}
void timerOnChange(TimerEvent event) {
if (event is StartTimer)
{
_timer=Timer.periodic(Duration(seconds: 1),_pushTimeOnTheStream);
}
else if(event is StopTimer){
//_timerController.sink.add(TimerState(false));
_timer?.cancel();
}
}
}
app
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const SplashWidget(),
);
}
}

How to use stateful widget parameters in state class at construction without adding the widget to the tree?

I stumped into a problem where I need to use a StatefulWidget parameter in its state class when it's constructed, but I couldn't find a way to do it since using widget.[whatever variable name] in the state's class constructor returns an unexpected null value, and the initState function only runs when the widget is being drawn to the screen.
For example:
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
final String text;
Test(this.text);
final state = _TestState();
#override
_TestState createState() => state;
}
class _TestState extends State<Test> {
String? changingText;
void updateChangingText(String moreText){
changingText = changingText! + moreText;
}
#override
void initState() {
super.initState();
changingText = widget.text;
}
#override
Widget build(BuildContext context) {
return Text(changingText!);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
var w = Test('test');
w.state.updateChangingText(' text');
return MaterialApp(home: Scaffold(body:
Test('test text')
));
}
}
void main() {
runApp(App());
}
This doesn't work since changingText is being updated before initState gives it its initial value since it only runs when Text is being drawn to the screen and this:
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
final String text;
Test(this.text);
final state = _TestState();
#override
_TestState createState() => state;
}
class _TestState extends State<Test> {
String? changingText;
void updateChangingText(String moreText){
changingText = changingText! + moreText;
}
_TestState(){
changingText = widget.text;
}
#override
Widget build(BuildContext context) {
return Text(changingText!);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
var w = Test('test');
w.state.updateChangingText(' text');
return MaterialApp(home: Scaffold(body:
Test('test text')
));
}
}
void main() {
runApp(App());
}
doesn't work either since you can't use widget.[whatever] in state class constructors (for some reason).
So what can I do to use widget parameters in the state class before the widget is drawn to the screen?
Thanks in advance for the help
You should use the initState method present in the State for this instead of the constructor
#override
void initState() {
changingText = widget.text;
super.initState();
}

Get class reference from routes flutter

Presume I have three classes: main, EndpointList and FillDataClass.
I have defined some routes in my main class as such:
void main() {
runApp(MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
'/': (context) => MyApp(),
'/endpoint_list': (context) => EndpointList(),
},
));
}
My EndpointList class is a simple list view:
import 'package:flutter/material.dart';
class EndpointData {
EndpointData(this.name, this.id, this.token, this.isIncoming);
final String name;
final String id;
final String token;
bool isIncoming;
}
class EndpointList extends StatefulWidget {
EndpointList({Key key}) : super(key: key);
#override
_EndpointList createState() => new _EndpointList();
}
class _EndpointList extends State<EndpointList> {
List<EndpointData> endpointList = <EndpointData>[];
#override
Widget build(BuildContext context) {
// build and show list
}
void insertEndpoint(EndpointData endpointData){
endpointList.add(endpointData);
}
}
My question is, how can I access and instance of EndpointList, from class that is not main, in order to call the insertEndpoint method?
In my java mind, I want to do this:
Endpoint endpoint = new Endpoint(); // This is done in route in main class
And then from class FillDataClass (presuming endpoint has been properly instanced in FillDataClass via constructor):
endpoint.insertEndpoint(data);
How can I create and access endpoint in order to populate, and then display, my list?
Use a separate the Endpoint class which will contain an insertEndPoint mothed.
class EndpointData {
EndpointData(this.name, this.id, this.token, this.isIncoming);
final String name;
final String id;
final String token;
bool isIncoming;
List<EndpointData> _endpointList = <EndpointData>[];
void insertEndpoint(EndpointData endpointData){
_endpointList.add(endpointData);
}
}
Then in your UI
class EndpointListUI extends StatefulWidget {
EndpointListUI({Key key}) : super(key: key);
#override
_EndpointListUI createState() => new _EndpointListUI();
}
class _EndpointListUI extends State<EndpointListUI> {
//You can create instance from anywhere and insert data to it
List<EndpointData> endpointList = <EndpointData>[];
endpointList.add(endpointData);
#override
Widget build(BuildContext context) {
// build and show list
}
}

shared_preferences returns null on existing value

I am trying to see if an id key is available in my app's shared_pereferences and if there is, redirect my user to the homepage. I am checking the Id in the initState() function of my main.dart and I know that the id exists because I can get it in other pages. but in my main.dart it returns null. any ideas?
here is my main.dart code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './ui/pages/auth/auth_one.dart';
import './ui/pages/main_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitDown, DeviceOrientation.portraitUp])
.then((_) => runApp(MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String userId;
#override
void initState() {
_getUserId().then((id) => userId = id);
super.initState();
}
#override
Widget build(BuildContext context) {
print(userId);
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.deepPurple),
debugShowCheckedModeBanner: false,
home: userId == null ? AuthOne() : MainPage(),
);
}
_getUserId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var id = prefs.getString('id');
return id;
}
}
Your _getUserId method is async, so you will have to refresh the widget after you get the result.
Use this:
#override
void initState() {
_getUserId().then((id) {
//calling setState will refresh your build method.
setState(() {
userId = id;
});
});
super.initState();
}
This is happening because you are trying to use the value before its calculated.
you could use timer function for delay