Hi im new in flutter and i have a class for notify changes of a global values in my app like this
import 'package:flutter/material.dart';
class GlobalNotifier {
final counter = ValueNotifier<int?>(null);
setValue(int value){
counter.value = value;
}
}
In another page i set the value with tap event executing this
var globalNotifier = GlobalNotifier();
globalNotifier.setValue(widget.item['id']);
I tested that the value is really changed and test the listener in the class GlobalNotifier its working fine but when i add a listener out of the class GlobalNotifier not notify nothing
var model = GlobalNotifier();
print('charged listeners');
model.counter.addListener(() {
print('counter changed!');
print(model.counter.value);
});
Thanks!
var model = GlobalNotifier();
print('charged listeners');
model.counter.addListener(() {
print('counter changed!');
print(model.counter.value);
});
model.setValue(0 or [youvalue]);
Related
I am relatively new at flutter. I am creating a timer app using bloc. In the timer you are supposed to have a session, then a break and so on and so forth. To track which type of session to start(a break or session), I am using a boolean value isBreak which I am tracking in the SessionsBloc.
Here is the definition of the SessionsState:
part of 'sessions_bloc.dart';
abstract class SessionsState extends Equatable { final bool isBreak; final int sessionCount; const SessionsState(this.isBreak, this.sessionCount);
#override List<Object> get props => []; }
class SessionsInitial extends SessionsState { const SessionsInitial(super.isBreak, super.sessionCount); }
class SessionTrackingState extends SessionsState { const SessionTrackingState(super.isBreak, super.sessionCount); }
I then use A BlocListener to check for the TimerFinishedState from another bloc TimerBloc, after which I add an event, SessionTrackingEvent, that is supposed to change the aforementioned boolean value.
Here is the code for the listener:
listener: (context, state) {
Task currentTask =
BlocProvider.of<TasksBloc>(context).state.currentTask;
bool isTimeBoxed = currentTask.isTimeBoxed;
int sessionDuration;
int breakDuration;
if (state is TimerCompleteState) {
//Get the SessionsBloc state
final sessionState = BlocProvider.of<SessionsBloc>(context).state;
//Get the current value of the isBreak boolean value
bool isBreak = sessionState.isBreak;
int sessionCount = sessionState.sessionCount;
//Print statements: Can't Debug properly yet:(
print(sessionState.isBreak);
print(sessionState.sessionCount);
if (isTimeBoxed) {
sessionDuration = currentTask.sessionTimeBox!;
breakDuration = currentTask.breakTimeBox ?? 2;
// sessionCount = HiveDb().getTaskSessionCount(currentTask.taskName);
} else {
sessionDuration = 5;
breakDuration = 3;
}
if (isBreak) {
//Set timer with duration time
BlocProvider.of<TimerBloc>(context)
.add(InitializeTimerEvent(duration: sessionDuration));
//Add Event to track session count and next countdown if break or session
BlocProvider.of<SessionsBloc>(context).add(SessionTrackingEvent(
isBreak: isBreak,
sessionCount: sessionCount,
));
} else {
//Add event to reset timer
BlocProvider.of<TimerBloc>(context)
.add(InitializeTimerEvent(duration: breakDuration));
//Emit a state that notifies Button Bloc that it's a break and deactivate repeat button.
BlocProvider.of<TimerBloc>(context)
.add(OnBreakEvent(duration: breakDuration));
//Add Event to track session count and next countdown if break or session
BlocProvider.of<SessionsBloc>(context).add(SessionTrackingEvent(
isBreak: isBreak,
sessionCount: sessionCount += 1,
));
}
}
},
Finally, in the SessionsBloc, I only have super constructor which initializes the boolean value to false and one event handler that is supposed to change it as appropriate.
class SessionsBloc extends Bloc<SessionsEvent, SessionsState> {
SessionsBloc() : super(const SessionsInitial(false, 0)) {
on<SessionTrackingEvent>((event, emit) {
emit(SessionTrackingState(
event.isBreak ? false : true, event.sessionCount));
});
}
}
The expected result is that for each SessionTrackingEvent added, the boolean should be toggled to the opposite value. However, what actually happens is that it Works the first time, turning the initialized value of false to true and from there it just stays the same. Here is a screenshot of my print statement which outputs the value of IsBreak after every call to SessionTrackingEvent.
Here is a screenshot of my print statement which outputs the value of IsBreak after every call to SessionTrackingEvent.
I have tried changing the variable type from final because I thought maybe it's a flutter constraint about reassigning variables.
I have tried moving the reading of the block state value into the build method outside of the listener because I thought maybe it doesn't read the value as frequently.
What could be the problem, what might be preventing the value from changing as appropriate?
You forgot to pass your SessionsState properties into props list, so the Bloc can't differentiate between old and new states without it.
abstract class SessionsState extends Equatable {
final bool isBreak;
final int sessionCount;
const SessionsState(this.isBreak, this.sessionCount);
#override
List<Object> get props => [isBreak, sessionCount]; // your props should go here like this
}
So Im trying to save an object(with only one String attribute) in the ObjectBox. The ObjectBox is called favoriteBox. With the onLongPress-function im trying to put the object in the box. In the initState-function im trying to put all the objects (more specific: the string attributes of each object) in to a nested list.
My problem now is that after calling the onLongPress-function and putting the object in the favoriteBox, the favoritBox is null according to the Debugger.
Im not sure where my mistake is.
class _GridViewJokesState extends State<GridViewJokes> {
late int seasonsListIndex;
List<List<String?>> seasonsList = seasons;
final toast = FToast();
final AudioPlayer audioPlayer = AudioPlayer();
late SeasonProvider seasonProvider;
Store? _store;
Box<FavoritesEntity>? favoritesBox;
#override
void initState() {
super.initState();
toast.init(context);
getApplicationDocumentsDirectory().then((dir) {
_store = Store(getObjectBoxModel(), directory: dir.path + "/objectbox");
});
favoritesBox = _store?.box<FavoritesEntity>();
seasonsList[5] =
favoritesBox?.getAll().map((e) => e.quoteName).toList() ?? [];
}
void onLongPress(listTileIndex) {
if (seasonProvider.seasonPicked) {
seasonsList[5].add(seasonsList[seasonsListIndex][listTileIndex]);
favoritesBox?.put(FavoritesEntity(
quoteName: seasonsList[seasonsListIndex][listTileIndex]));
toast.showToast(
child: buildToast("Zu Favoriten hinzugefĆ¼gt"),
gravity: ToastGravity.BOTTOM);
}
import 'package:objectbox/objectbox.dart';
#Entity()
class FavoritesEntity {
FavoritesEntity({required this.quoteName});
int id = 0;
String? quoteName;
}
The issue is that the code does not await the Future that returns the application Documents directory before accessing store. So store will still be null and the following line that gets a box does nothing.
getApplicationDocumentsDirectory().then((dir) {
_store = Store(getObjectBoxModel(), directory: dir.path + "/objectbox");
});
// store is still null here:
favoritesBox = _store?.box<FavoritesEntity>();
You can either move the box get code into the then() closure. Or, as we recommend, init Store into a global variable using a helper class in the main function. See our examples.
I have a class Too
class Too{
bool isLogged = false;
BehaviorSubject suject = BehaviorSubject<bool>();
Too({required this.isLogged}){
suject = new BehaviorSubject<bool>.seeded(isLogged);
}
void login(){
isLogged = true;
suject.sink.add(isLogged);
}
void logOut(){
isLogged = false;
suject.sink.add(isLogged);
}
void dispose(){
suject.close();
}
and I also have the Foo class:
class Foo{
Too _too = new Too(isLogged: false);
_too.stream.listen((event) { print('${event}');});
}
My issue is When the user is calling the login() method of the Too class nothing happens at the level of the Foo class.
What I want to do is that if the user calls the login() method of the Too class and his isLogged attribute is set to true, then this change is done at the level of all the classes that have an attribute of the Too type.
Note: It's much easier to do it with Angular or Ionic using RxJS, but with dart, I don't know how to implement this mechanism.
Foo is not reacting because its listening to a different instance of Too.
The way you have it is that each new instance of Foo creates a new instance of Too. If I understand you correctly, you want all instances of Foo to react to any change to a single instance of Too.
You can use a singleton for this.
class Too {
// one of a few ways to make a singleton in Dart
Too._();
static final _instance = Too._();
factory Too() {
return _instance;
}
final subject = BehaviorSubject<bool>.seeded(isLogged);
static bool isLogged = false;
void login() {
isLogged = true;
subject.sink.add(isLogged);
}
void logOut() {
isLogged = false;
subject.sink.add(isLogged);
}
void dispose() {
subject.close();
}
}
Now you can have any Foo object listen to the same Too instance.
class Foo {
Foo() {
Too().subject.stream.listen((event) {
print('foo $event'); // this will now print whenever a subject from your Too class is updated.
});
}
}
Now for example you could test this by creating a button with this as the onPressed.
onPressed: () {
final foo = Foo(); // just created an example of a Foo object that will
// print the updated value of the Too singleton
Too().login();
},
RxDart is great. However when it comes to reactive programming in Flutter, I suggest checking out Get X as it simplifies a lot of stream based stuff.
As you can see from my code sample, I'm using this variable. I also reference multiple times later in the class.
Flutter Warning - info: The value of the field '_loadTimer' isn't used. (unused_field at [app] lib/models/knowledge_level/pb_cycle_permissions_collection.dart:12)
ng is: info: The value of the field '_loadTimer' isn't used. (unused_field at [app] lib/models/knowledge_level/pb_cycle_permissions_collection.dart:12)
import 'dart:async';
import 'dart:collection';
import 'package:app/data/graphql/queries.dart';
import 'package:app/helpers/shared_logger.dart';
import 'package:flutter/cupertino.dart';
import '../command_permission.dart';
class PBCyclePermissionsCollection
with ListMixin<CommandPermission>, ChangeNotifier {
Timer? _loadTimer;
///
/// CONSTRUCTION AND INITIALIZATION
///
static final PBCyclePermissionsCollection _instance =
PBCyclePermissionsCollection._internal();
factory PBCyclePermissionsCollection() {
return _instance;
}
/// ACCESS SINGLETON VIA myPBCyclePermInstance = PBCyclePermissionsCollection()
PBCyclePermissionsCollection._internal() {
_loadTimer = Timer(_waitFirstLoad, _attemptLoad);
}
///
/// PRIVATE VARIABLES AND METHODS
///
static final Duration _waitFirstLoad = Duration(milliseconds: 500);
static final Duration _waitRetryLoad = Duration(seconds: 2);
static final int _maxAttempts = 4;
int _loadAttempts = 0;
bool _isReady = false;
bool _hasFailed = false;
/// Storage of CommandPermissions List once loaded
final List<CommandPermission> _list = [];
void _attemptLoad() async {
_loadAttempts++;
SharedLogger.I().d('_attemptLoad() current load attempt: ${_loadAttempts}');
try {
final results = await Queries.getCommandPermissions();
var data = results.data!['commandPermissions'];
var permissions = <CommandPermission>[];
for (var item in data) {
permissions.add(CommandPermission.fromJson(item));
}
/// Populated class with loaded objects.
_list.clear();
_list.addAll(permissions);
_isReady = true;
notifyListeners();
} catch (e) {
SharedLogger.I().e('Error loading PBCycle Permissions - ${e}');
_newAttempt();
}
}
void _newAttempt() {
SharedLogger.I().d(
'_newTry() _loadAttempts: ${_loadAttempts} _maxAttempts:${_maxAttempts} '
'creating new loadTimer for another try? : ${!(_loadAttempts >= _maxAttempts)}');
if (_loadAttempts >= _maxAttempts) {
_hasFailed = true;
notifyListeners();
// TODO: do we invalidate any existing data that may have been loaded before? Like if this load cycle is a refresh?
// If so, we should reset _isReady and _list;
return;
}
_loadTimer = Timer(_waitRetryLoad, _attemptLoad);
}
///
/// PUBLIC METHODS
///
bool get isLoaded {
return _isReady;
}
bool get hasFailed {
return _hasFailed;
}
#override
set length(int newLength) {
throw ('length cannot be changed externally');
}
#override
int get length {
return _list.length;
}
#override
CommandPermission operator [](int index) {
return _list[index];
}
#override
void operator []=(int index, CommandPermission value) {
throw ('Cannot modify list from outside');
}
}
Image of IDE with Code Sample and associated Dart Analysis Hints
You aren't actually using it, you're just setting the value multiple times
The answer from Andrew is correct, but a bit unclear since unsure what 'it' refers to. Here's another way to explain what the warning message means:
Notice that the message says you are not using the value. You are using the variable, but not its value. You are assigning the value. To read the value would be using it.
That said, the question is answered, but I think the question is somewhat vague by asking "what am i missing". What do you (OP) want to achieve? I assume it's to not see that warning anymore. And that is what brings me to this post. I have similar issue. I too have a class variable for a Timer and I get this same warning message. One does not need to read the value in order to use a timer but the analyzer doesn't know that. While writing this response I have discovered that you can a suppress warning. How about this:
// ignore: unused_field
Timer? _loadTimer;
I have a DownloadsService class that handles downloading of file using dio package. I want to listen to the download progress from my ViewModel class that implements the downloadFile method inside my DownloadService class. How do I do this?
Here's my code snippet for DownloadsService class:
class DownloadsService {
final String urlOfFileToDownload = 'http://justadummyurl.com/'; //in my actual app, this is user input
final String filename = 'dummyfile.jpg';
final String dir = 'downloads/$filename'; //i'll have it saved inside internal storage downloads directory
void downloadFile() {
Dio dio = Dio();
dio.download(urlOfFileToDownload, '$dir/$filename', onReceiveProgress(received, total) {
int percentage = ((received / total) * 100).floor(); //this is what I want to listen to from my ViewModel class
});
}
}
and this is my ViewModel class:
class ViewModel {
DownloadsService _dlService = DownloadsService(); //note: I'm using get_it package for my services class to make a singleton instance. I just wrote it this way here for simplicity..
void implementDownload() {
if(Permission.storage.request().isGranted) { //so I can save the file in my internal storage
_dlService.downloadFile();
/*
now this is where I'm stuck.. My ViewModel class is connected to my View - which displays
the progress of my download in a LinearProgressIndicator. I need to listen to the changes in
percentage inside this class.. Note: my View class has no access to DownloadsService class.
*/
}
}
}
The Dio documentation provides an example on how to make the response type into a stream/byte.. But it doesn't give any example on how to do it when downloading a file. Can someone point me to a right direction? I'm really stuck at the moment.. Thank you very much!
I had exactly this issue
I solved issue with dependency injection from my model
actually I defined progress field(double) into my model for listening to download percent and also defined an object from my model into GetX controller(u can use any dependency injection like getIt, ...) so with live data mechanism , this issue will solve so easily
If the View creates the ViewModel, then you must define PublishSubject variable in View class, then pass it to the ViewModel, and also pass it to the DownloadsService as a parameter
like this :
class ViewModel {
PublishSubject publishSubject;
ViewModel(this.publishSubject);
DownloadsService _dlService = DownloadsService();
void implementDownload() {
if(Permission.storage.request().isGranted) {
_dlService.downloadFile(publishSubject);
}
}
}
So that the View listens to the changes that will happen before downloadFile method, Which in turn sends the changes sequentially
like this:
void downloadFile(PublishSubject publishSubject) {
Dio dio = Dio();
dio.download(urlOfFileToDownload, '$dir/$filename',
onReceiveProgress(received,total) {
int percentage = ((received / total) * 100).floor();
publishSubject.add(percentage);
});
}
So that the interface listens to the changes that will happen before
like this:
class View {
PublishSubject publishSubject = PublishSubject();
ViewModel viewModel;
View(){
publishSubject.listen((value) {
// the value is percentage.
//can you refresh view or do anything
});
viewModel = ViewModel(publishSubject);
}
}