How to listen to MyImage update event for webp animation in flutter - flutter

I want to listen a webp animation's update event on each webp frame, so I create MyImage. but I can't get onUpdateImage trigger, please help. thx
main.dart
AssetImage loadWebp2() {
print('WEBP::loadWebp2');
final image = 'assets/image/07_OK.webp';
var imageProvider = AssetImage(image);
var myImage = MyImage(imageProvider: imageProvider);
myImage.addListener(MyStateListener());
return imageProvider;
}
class MyStateListener extends StateListener {
#override
void onUpdateImage(ImageInfo imageInfo) {
super.onUpdateImage(imageInfo);
print('WEBP::onUpdateImage, $imageInfo');
}
}
MyImage.dart
import 'package:flutter/cupertino.dart';
class MyImage extends StatefulWidget {
const MyImage({
Key key,
#required this.imageProvider,
})
: assert(imageProvider != null),
super(key: key);
final ImageProvider imageProvider;
#override
_MyImageState createState() => _MyImageState();
}
class _MyImageState extends State<MyImage> {
ImageStream _imageStream;
ImageInfo _imageInfo;
StateListener stateListener;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_getImage();
}
#override
void didUpdateWidget(MyImage oldWidget) {
super.didUpdateWidget(oldWidget);
print('WEBP::didUpdateWidget');
if (widget.imageProvider != oldWidget.imageProvider)
_getImage();
}
void _getImage() {
print('WEBP::_getImage');
final ImageStream oldImageStream = _imageStream;
_imageStream =
widget.imageProvider.resolve(createLocalImageConfiguration(context));
if (_imageStream.key != oldImageStream?.key) {
final ImageStreamListener listener = ImageStreamListener(_updateImage);
oldImageStream?.removeListener(listener);
_imageStream.addListener(listener);
}
}
void _updateImage(ImageInfo imageInfo, bool synchronousCall) {
print('WEBP::_updateImage');
setState(() {
// Trigger a build whenever the image changes.
_imageInfo = imageInfo;
if (stateListener != null) {
stateListener.onUpdateImage(_imageInfo);
}
print('WEBP::_updateImage, setState, $imageInfo');
});
}
#override
void dispose() {
_imageStream.removeListener(ImageStreamListener(_updateImage));
super.dispose();
}
#override
Widget build(BuildContext context) {
print('WEBP::build');
return RawImage(
image: _imageInfo?.image, // this is a dart:ui Image object
scale: _imageInfo?.scale ?? 1.0,
);
}
}
class StateListener {
void onUpdateImage(ImageInfo imageInfo) {}
}

Related

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 access Riverpod StateNotifier state outside build without ConsumerWidget or HookWidget?

I have this class:
class BeatCounter extends StateNotifier<int> {
BeatCounter() : super(8);
int get counter => state;
void increment() {
if (state < 16) {
state++;
print('State $state');
} else
return;
}
void decrement() {
if (state > 1) {
state--;
} else
return;
}
}
final beatCounterProvider = StateNotifierProvider((ref) => BeatCounter());
And want to access the state inside a class that extends a StatefullWidget that I don't want (/know how to) change. So I can't use 'with' to extend 'ConsumerWidget' or 'HookWidget'.
How do I get the state in this class?
class ChordsTrack extends BaseWidget {
ChordsTrack({Key key, #required this.sample}) : super(key: key);
final SOUND_SAMPLE sample;
#override
_ChordsTrackState createState() => _ChordsTrackState();
}
class _ChordsTrackState extends BaseState<ChordsTrack> {
MultitrackChordBassBoxCreator multitrackBox =
MultitrackChordBassBoxCreator();
List<bool> _data = List.generate(***BeatCounter().state***, (i) => false);
#override
void on<Signal>(Signal signal) {
setState(() => _data = AudioEngine.trackdata[widget.sample]);
}
...
}
Newbie question, I know, but would really appreciate some help.
I solved it this way.
class BeatCounter extends StateNotifier<int> {
BeatCounter() : super(8);
static int counter = 8; //*
void increment() {
if (state < 16) {
state++;
counter++; //*
} else
return;
}
void decrement() {
if (state > 1) {
state--;
counter--;//*
} else
return;
}
}
Is there a better option?
Just use context.read(beatCounterProvider) in your initState.
class _ChordsTrackState extends BaseState<ChordsTrack> {
MultitrackChordBassBoxCreator multitrackBox =
MultitrackChordBassBoxCreator();
List<bool> _data;
#override
void initState(){
super.initState();
_data = List.generate(context.read(beatCounterProvider).state, (i) => false);
}
#override
void on<Signal>(Signal signal) {
setState(() => _data = AudioEngine.trackdata[widget.sample]);
}
...
}

Using flutter HookWidget and didChangeAppLifecycleState

How can I monitor the life cycle states of the app from a particular page using HookWidget the way you can with a Stateful widget?
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) {
...
}
if (state == AppLifecycleState.resumed) {
...
}
if (state == AppLifecycleState.detached) {
...
}
}
First make a class:
class MyObserver implements WidgetsBindingObserver {
}
Then create it and register it with:
Widget build(BuildContext) {
useEffect(() {
final observer = MyObserver();
WidgetsBinding.instance.addObserver(observer);
return () => WidgetsBinding.instance.removeObserver(observer);
}, const []);
...
}
Flutter hooks is shipped with an inbuilt didchangeapplifecycle
access it as follows
final appLifecycleState = useAppLifecycleState();
useEffect(() {
print("current app state");
print(appLifecycleState);
if (appLifecycleState == AppLifecycleState.paused || appLifecycleState == AppLifecycleState.inactive) {
//...
} else if (appLifecycleState == AppLifecycleState.resumed) {
//...
}
return null;
}, [appLifecycleState]);
In the docs here search for "ways to create a hook". You'll see there are 2 ways of creating a hook, using a function or using a class. You are going for the "using a class" one. Then use initHook override as your initState and dispose works the same. Thats how I implemented it on my end.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
useWidgetLifecycleObserver(BuildContext context) {
return use(const _WidgetObserver());
}
class _WidgetObserver extends Hook<void> {
const _WidgetObserver();
#override
HookState<void, Hook<void>> createState() {
return _WidgetObserverState();
}
}
class _WidgetObserverState extends HookState<void, _WidgetObserver> with WidgetsBindingObserver {
#override
void build(BuildContext context) {}
#override
void initHook() {
super.initHook();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("app state now is $state");
super.didChangeAppLifecycleState(state);
}
}
Then
class Root extends HookWidget {
#override
Widget build(BuildContext context) {
useWidgetLifecycleObserver(context);
I've just had to deal with the same problem. And here is my solution using custom hooks:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
AppLifecycleState useAppLifecycleState() {
return use(const _LifeCycleState());
}
class _LifeCycleState extends Hook<AppLifecycleState> {
const _LifeCycleState();
#override
__LifeCycleState createState() => __LifeCycleState();
}
class __LifeCycleState extends HookState<AppLifecycleState, _LifeCycleState>
with WidgetsBindingObserver {
AppLifecycleState _theState;
#override
void initHook() {
super.initHook();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
setState(() {
_theState = state;
});
}
#override
AppLifecycleState build(BuildContext context) {
return _theState;
}
#override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
}
And in the HookWidget that you want to access the app lifecycle state use the useEffect :
final appLifecycleState = useAppLifecycleState();
useEffect(() {
print("current app state");
print(appLifecycleState);
if (appLifecycleState == AppLifecycleState.paused ||
appLifecycleState == AppLifecycleState.inactive) {
//...
} else if (appLifecycleState == AppLifecycleState.resumed) {
//...
}
return null;
}, [appLifecycleState]);

inheritFromWidgetOfExactType(InheritedProvider<ConnectivityStatus>) or inheritFromElement() was called before

In this simple class i want to make base state class to manage some actions such as accessing to internet connection:
abstract class BaseState<T extends StatefulWidget> extends State {
bool isOnline;
ConnectivityStatus _connectivityStatus;
#override
void initState() {
super.initState();
_connectivityStatus = Provider.of<ConnectivityStatus>(context);
isOnline = _connectivityStatus == ConnectivityStatus.Connected;
if (!isOnline) {
showSimpleNotification(Text("disconnected"), background: Colors.green);
} else {
showSimpleNotification(Text("connected"), background: Colors.red);
}
}
}
when i try to use this class like with:
class _FragmentLoginState extends BaseState<FragmentLogin> with SingleTickerProviderStateMixin {
PageController _pageController;
Color left = Colors.black;
Color right = Colors.white;
#override
void initState() {
super.initState();
_pageController = PageController(initialPage: 1);
}
#override
Widget build(BuildContext context) {
}
}
the problem is you don't have a valid Context yet.
you can try theses two solution
defer using context:
#override
void initState() {
super.initState();
_initConnectivity();
}
Future _initConnectivity() async {
await Future.delayad(Duration.zero);
_connectivityStatus = Provider.of<ConnectivityStatus>(context);
isOnline = _connectivityStatus == ConnectivityStatus.Connected;
if (!isOnline) {
showSimpleNotification(Text("disconnected"), background: Colors.green);
} else {
showSimpleNotification(Text("connected"), background: Colors.red);
}
}
move your logic in the build function ( in BaseState )
_initConnectivity() {
_connectivityStatus = Provider.of<ConnectivityStatus>(context);
isOnline = _connectivityStatus == ConnectivityStatus.Connected;
if (!isOnline) {
showSimpleNotification(Text("disconnected"), background: Colors.green);
} else {
showSimpleNotification(Text("connected"), background: Colors.red);
}
}
#override
Widget build(BuildContext context) {
_initConnectivity();
}
in the second method you can also create a flag like isFirstBuild to make sure it runs only one time.
also these may help :
initialize data once in initState and call the setState when data is ready causes exception
Flutter get context in initState method
Use a frame callback which delays execution until the next frame (i.e. after initState has complete)
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
_connectivityStatus = Provider.of<ConnectivityStatus>(context);
isOnline = _connectivityStatus == ConnectivityStatus.Connected;
if (!isOnline) {
showSimpleNotification(Text("disconnected"), background: Colors.green);
} else {
showSimpleNotification(Text("connected"), background: Colors.red);
}
});
}

Where I can get information to make the WidgetsBindingObserver works again?

After upgrading to Flutter 0.7.3 channel beta (Dart 2.1.0-dev.1.0.flutter-ccb16f7282) the WidgetsBindingObserver is not working.
It worked before and after login if AppLifecycleState paused, inactive or suspended it returns to main page. But seems it doesnt work with new update. My question is where I can get information to see how to make the WidgetsBindingObserver works again.
// Statefull HomePage
class PersonalLoginPage extends StatefulWidget {
const PersonalLoginPage({ Key key }) : super(key: key);
#override
_PersonalLoginPageState createState() => new _PersonalLoginPageState();
}
class _PersonalLoginPageState extends State<PersonalLoginPage> with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
bool _appStatePause;
// TODO: initState function
#override
void initState() {
print("initState Starting Now .......................");
super.initState();
authenticateUser();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
Timer _timer;
setState(() {
_appLifecycleState = state;
if (_appLifecycleState == AppLifecycleState.paused ||
_appLifecycleState == AppLifecycleState.inactive ||
_appLifecycleState == AppLifecycleState.suspending) {
_appStatePause = true;
print("New Timer Starting Now .......................");
_timer = Timer.periodic(Duration(seconds: 60), _callback);
} else {
_appStatePause = false;
}
});
}
void _callback(_timer) {
if (_appStatePause == true) {
print("Timer Finished without cancel...................");
setState(() {
Navigator.push(
context,
SlideRightRoute(widget: MyApp()),
);
});
} else {
_timer.cancel();
print("Timer cancel now................................");
}
}
// TODO: authenticateUser function
Future authenticateUser() async {
……
……
……
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// TODO: main build Widget
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: new Text(“Hello World”)
)
);
}