I wanted to make a timer based on the Internet cubit. when Internet Disconnected State is emitted timer should be reset. But after reset(when Internet Disconnect state emitted) it is emitting increment timer state again.
Home Cubit (timer cubit)
class HomeCubit extends Cubit<HomeState> {
HomeCubit() : super(HomeState.initial());
void increment() {
Timer.periodic(const Duration(seconds: 1), (timer) {
emit(state.copyWith(counter: state.counter + 1));
});
}
void reset() {
emit(
state.copyWith(counter: 0),
);
}
}
Home State (timer state)
class HomeState extends Equatable {
final int counter;
const HomeState({
required this.counter,
});
factory HomeState.initial() {
return const HomeState(counter: 0);
}
HomeState copyWith({
int? counter,
}) {
return HomeState(
counter: counter ?? this.counter,
);
}
#override
List<Object?> get props => [counter];
}
Internet Cubit
class InternetCubit extends Cubit<InternetState> {
final Connectivity connectivity;
late StreamSubscription internetStreamSubscription;
InternetCubit({required this.connectivity}) : super(InternetInitial()) {
monitorInternet();
}
void monitorInternet() {
internetStreamSubscription = connectivity.onConnectivityChanged.listen((connectivityResult) {
if (connectivityResult == ConnectivityResult.wifi) {
emit(const InternetConnected(connectionType: ConnectionType.wifi));
} else if (connectivityResult == ConnectivityResult.mobile) {
emit(const InternetConnected(connectionType: ConnectionType.mobileData));
} else if (connectivityResult == ConnectivityResult.none) {
emit(InternetDisconnected());
}
});
}
#override
Future<void> close() {
internetStreamSubscription.cancel();
return super.close();
}
}
Internet State
enum ConnectionType { wifi, mobileData }
abstract class InternetState extends Equatable {
const InternetState();
#override
List<Object> get props => [];
}
class InternetInitial extends InternetState {}
class InternetConnected extends InternetState {
final ConnectionType connectionType;
const InternetConnected({required this.connectionType});
#override
List<Object> get props => [connectionType];
}
class InternetDisconnected extends InternetState {}
Homepage view
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
return BlocListener<InternetCubit, InternetState>(
listener: (context, state) {
if (state is InternetConnected) {
context.read<HomeCubit>().increment();
} else if (state is InternetDisconnected) {
context.read<HomeCubit>().reset();
}
},
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have been connected to the Internet for the past'),
Text(context.watch<HomeCubit>().state.counter.toString()),
],
),
),
),
);
}
}
I tried Cubit stream subscribe too. but why it is re-emitting old state again? why timer not staying in 0 or reset state?
If I understand you correctly you want the timer to stay 0 when you call reset() in HomeCubit().
Try this:
class HomeCubit extends Cubit<HomeState> {
HomeCubit() : super(HomeState.initial());
Timer? timer;
void increment() {
timer ??= Timer.periodic(const Duration(seconds: 1), (timer) {
emit(state.copyWith(counter: state.counter + 1));
});
}
void reset() {
timer?.cancel();
timer = null;
emit(
state.copyWith(counter: 0),
);
}
}
Related
I have a simple timer which works fine when the app is running in foreground. I can listen to the stream and update the UI. However when the app is in the background it will not continue counting. How can I continue counting when the app is running in the background?
This is my code for the timer:
class SetTimer {
int _seconds = 0;
final _streamController = StreamController<int>.broadcast();
Timer? _timer;
// Getters
Stream<int> get stream => _streamController.stream;
// Setters
void start() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_seconds++;
_updateSeconds();
});
}
void _updateSeconds() {
// stop counting after one hour
if (_seconds < 3600) {
_streamController.sink.add(_seconds);
}
}
}
Try the below code -
I tested it & found it will count the number in the background & there is no problem.
I added a screen record video here, it will help you to understand.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyTest(),
);
}
}
class MyTest extends StatefulWidget {
const MyTest({Key? key}) : super(key: key);
#override
State<MyTest> createState() => _MyTestState();
}
class _MyTestState extends State<MyTest> {
final SetTimer _setTimer = SetTimer();
#override
void initState() {
_setTimer.start();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<int>(
stream: _setTimer.stream,
builder: (
BuildContext context,
AsyncSnapshot<int> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.active
|| snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
return Center(
child: Text(
snapshot.data.toString(),
style: const TextStyle(color: Colors.red, fontSize: 40)
),
);
} else {
return const Text('Empty data');
}
} else {
return Text('State: ${snapshot.connectionState}');
}
},
),
);
}
}
class SetTimer {
int _seconds = 0;
final _streamController = StreamController<int>.broadcast();
Timer? _timer;
// Getters
Stream<int> get stream => _streamController.stream;
// Setters
void start() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_seconds++;
_updateSeconds();
});
}
void _updateSeconds() {
// stop counting after one hour
if (_seconds < 3600) {
_streamController.sink.add(_seconds);
}
}
}
As far as I know, there is no way to run the app in the background, like you want to. I had a similar problem with an app, I developed and didn't found the perfect solution.
As a workaround, I'm using the wakelock package to prevent the mobile device / app from going into standby. Maybe this could also be a solution for you.
I'm using getit package from flutter. How to listen to changes of a singleton class that extends changeNotifer with getit package?
class UserService extends ChangeNotifier {
Database _db = serviceLocator<Database>();
User user;
Future<void> loadUserFromDatabase(String userId) async {
User user = await _db.getUser(userId);
if (user != null) {
updateUser(user: user);
}
}
void updateUser({
User user,
}) {
this.user = user;
notifyListeners();
}
}
// SERVICE LOCATOR
GetIt serviceLocator = GetIt.instance;
void setupServiceLocator() {
serviceLocator.registerLazySingleton<Database>(
() => DatabaseImpl(),
);
serviceLocator.registerLazySingleton<UserService>(
() => UserService(),
);
}
class UserDetailsPage extends StatelessWidget {
final _service = serviceLocator<UserService>();
#override
Widget build(BuildContext context) {
return return Container(
padding: const EdgeInsets.all(8.0),
child: Text(
_service.user != null
? _service.user.name
: "",
),
);
}
In the above code, how to reflect changes in name to UserDetailsPage?
you can use the StatefullWidget to achieve your requirement and easy to update the details page.
Add the listener and notify the widget on every time updating it.
class UserDetailsPage extends StatefulWidget {
#override
_UserDetailsPageState createState() => _UserDetailsPageState();
}
class _UserDetailsPageState extends State<UserDetailsPage> {
final _service = serviceLocator<UserService>();
void initState(){
super.initState();
_service?.addListener(updateUserDetails);
}
void updateUserDetails(){
setState((){});
}
#override
Widget build(BuildContext context) {
return return Container(
padding: const EdgeInsets.all(8.0),
child: Text(
userService.user != null
? _service.user.name
: "",
),
);
}
}
I'm creating an app that has a start button (class StartButton) and i want that button calls a method from another class (class NewRide), but how can i call a void function from another file?
class StartButton extends StatefulWidget{
#override
_StartButtonState createState() => _StartButtonState();
}
class _StartButtonState extends State<StartButton> {
String _driverState = 'offline';
#override
Widget build(context){
return FlatButton(
child: Text('Start'),
onPressed: (){
setState(() {
if (_driverState == 'offline') {
_driverState = 'online';
} else {
_driverState = 'offline';
}
});
},
);
}
}
This is New Ride class, it has the void function i want to call when i press start button.
class NewRide extends StatefulWidget {
#override
_NewRideState createState() => _NewRideState();
}
class _NewRideState extends State<NewRide> {
int _counter = 60;
Timer _timer;
#override
Widget build(BuildContext context) {
void startTimer() {
_counter = 60;
if (_timer != null) {
_timer.cancel();
}
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
if (_counter > 0) {
_counter--;
} else {
_timer.cancel();
}
});
});
}
_showNewRideAlert() {
if (_counter == 0) {
return Text('Counter == 0');
} else {
return Text('Counter != 0');
}
}
return _showNewRideAlert();
}
}
use GlobalKey and make startTimer public and change NewRideState to _ NewRideState
class StartButton extends StatefulWidget {
#override
_StartButtonState createState() => _StartButtonState();
}
class _StartButtonState extends State<StartButton> {
String _driverState = 'offline';
GlobalKey<NewRideState> newRideKey = GlobalKey<NewRideState>();
#override
Widget build(context) {
return Column(
children: [
NewRide(key: newRideKey),
FlatButton(
child: Text('Start'),
onPressed: () {
setState(() {
if (_driverState == 'offline') {
_driverState = 'online';
} else {
_driverState = 'offline';
}
});
},
),
],
);
}
}
class NewRide extends StatefulWidget {
const NewRide({Key key}) : super(key: key);
#override
NewRideState createState() => NewRideState();
}
class NewRideState extends State<NewRide> {
int _counter = 60;
Timer _timer;
#override
Widget build(BuildContext context) {
_showNewRideAlert() {
if (_counter == 0) {
return Text('Counter == 0');
} else {
return Text('Counter != 0');
}
}
return _showNewRideAlert();
}
void startTimer() {
_counter = 60;
if (_timer != null) {
_timer.cancel();
}
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
if (_counter > 0) {
_counter--;
} else {
_timer.cancel();
}
});
});
}
}
First import the other file/class into the main class (I'm not sure what your file is called).
import '.../newRide.dart' as newRide;
Then call the function like this:
newRide._showNewRideAlert();
It might not work due to how the class/state is setup though (the widget won't be built). So you may have to move the code into a place that is callable, such as the top of the class or implement the function into the page where it is used.
What am I doing wrong here that my state in a Bloc Pattern changes only one time then mapEventToState doesn't react to BlocProvider.of<CounterBloc>(context).add(ActiveEvent()); request?
I am trying to get into the way of things with the Bloc Pattern but when I switch state in the switcher on a counter page the state changes and after that, it doesn't update at all. It's like don't go any further from onChanged switch function.
I guess the issue is in my stream subscription which is implemented in the CounterBloc contractor. Or I return the state incorrectly.
I would appreciate your help and if you explain to me the mistake.
my bloc
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:practicing_bloc/blocs/counter/counterEvents.dart';
import 'package:practicing_bloc/blocs/counter/counterState.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
#override
CounterState get initialState => Active(active: true, count: 0);
CounterBloc() {
_counterStream = _counter.stream;
}
StreamController<CounterState> _counter = StreamController<CounterState>();
Stream<CounterState> _counterStream;
#override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
CounterState currentState = state;
print('currect: $currentState');
if (event is ActiveEvent) {
_counter.add(Active(active: true, count: currentState.count));
yield* _counterStream;
} else if (event is InactiveEvent) {
_counter.add(Inactive(active: false, count: currentState.count));
yield* _counterStream;
}
}
}
bloc state
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
abstract class CounterState extends Equatable {
final bool active;
final int count;
const CounterState({#required this.active, #required this.count});
#override
List<Object> get props => [active, count];
#override
String toString() => 'State { active : $active, count : $count }';
}
class Active extends CounterState {
const Active({#required bool active, #required int count})
: super(active: active, count: count);
}
class Inactive extends CounterState {
const Inactive({#required bool active, #required int count})
: super(active: active, count: count);
}
bloc Event
import 'package:equatable/equatable.dart';
abstract class CounterEvent extends Equatable {
const CounterEvent();
#override
List<Object> get props => [];
}
class Increase extends CounterEvent {}
class Decrease extends CounterEvent {}
class ActiveEvent extends CounterEvent {}
class InactiveEvent extends CounterEvent {}
counterPage
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:practicing_bloc/blocs/counter/counterBloc.dart';
class CounterPage extends StatefulWidget {
#override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
bool stateActive = false;
#override
Widget build(BuildContext context) {
//ignore: close_sinks
dynamic counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Flutter Counter | Page title')),
body: SafeArea(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
String stateString = state.active ? 'Active' : 'Inactive';
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Counter is : $stateString'),
Text('Current counter is : ${state.count}'),
Switch(
value: stateActive,
onChanged: (bool value) {
print(counterBloc.state);
setState(() {
stateActive = value;
});
CounterEvent newEvent =
value ? ActiveEvent() : InactiveEvent();
counterBloc.add(newEvent);
// print('BloC state: ${counterBloc.state.active} | switch state: ${state.active}');
},
)
],
),
);
},
),
),
);
}
}
Basically instead of yielding * _counterStream you need to yield the states in this i.e. Active or Inactive
Change this
if (event is ActiveEvent) {
_counter.add(Active(active: true, count: currentState.count));
yield* _counterStream;
} else if (event is InactiveEvent) {
_counter.add(Inactive(active: false, count: currentState.count));
yield* _counterStream;
}
to this
if (event is ActiveEvent) {
yield Inactive(active: false, count: currentState.count);
} else if (event is InactiveEvent) {
yield Active(active: true, count: currentState.count);
}
I'm facing a problem in BlocLibrary(https://bloclibrary.dev/)
I need to receive same state in BlocListner/BlocBuilder, let me explain in code:
This is Bloc(for little explain):
class CounterBloc extends Bloc<CounterEvent, AppState> {
#override
AppState get initialState => InititalState();
#override
Stream<AppState> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.getState:
yield Counter(value: 0);
break;
}
HERE IS STATE CLASS:
import 'package:flutter_bloc/flutter_bloc.dart';
enum StateEvent { getState }
abstract class AppState extends Equatable {
const AppState();
#override
List<Object> get props => [];
}
class Counter extends AppState {
final int count;
const Counter({#required this.count});
#override
List<Object> get props => [count];
#override
String toString() => 'Counter { count: $count }';
}
Here you go for my bloc listener/builder:
BlocListener<CounterBloc, AppState>(
listener: (context, state) {
if (state is Counter) {
**Here I needed same state, means if I press getState, it should print 0 everytime**
print(state. value);
}
},
child: BlocBuilder<CounterBloc, AppState>(
builder: (context, state) {
return Center(
child: Text(
'${value}',
style: TextStyle(fontSize: 24.0),
),
);
},
),
)
Change your AppState, you can read more about Equatable usage here. This post is mostly a duplication of this.
You should not use Equatable if you want the same state back-to-back to trigger multiple transitions.
abstract class AppState {
const AppState();
}
class Counter extends AppState {
final int count;
const Counter({#required this.count});
#override
String toString() => 'Increment { count: $count }';
}