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.
I'd like to have a property which is settable only once and then ignores further attempts to set it, so noone can accidentally set or reset the value.
I know that late can "kinda" do this, but when using late, neither the compiler, nor the IDE tell me that calling doOther() after calling doOnce() will cause an exception.
class Foo {
bool? _bar;
void doOnce() {
if (_bar == null) {
_bar = true;
print('done this once');
}
}
void doOther() {
_bar = false; // this should not be possible when doOnce() is done
print('done other');
}
}
void main() {
Foo foo = Foo();
foo.doOnce();
}
Using late final is as close as it gets:
class Foo {
late final int a;
Foo();
void doThing() {
a = 5;
}
void doAnotherThing() {
a = 3;
}
}
...
void main() {
final foo = Foo();
// Uncaught Error: LateInitializationError: Field 'a' has not been initialized.
// print(foo.a);
foo.doThing();
// Prints: 5
print(foo.a);
// Uncaught Error: LateInitializationError: Field 'a' has already been initialized.
// foo.doAnotherThing();
}
This gives you the runtime behavior you are looking for, but there is no solution that gives you static compile-time checking for assigning to a late (or late-ish) variable twice. That's because in order to know that your program is attempting to do so, the compiler would have to inspect every path of your program that could possibly assign to the late variable which could take a really long time.
In short, you are going to have to practice your own due diligence instead on relying on the compiler or static analyzer to inform you of your mistakes.
I have two classes: CurrencyRepo and CurrencyFetcher. From CurrencyFetcher I try to listen to the behaviorSubject that is in the repo. But when I add values in the behaviorSubject inside the repo, CurrencyFetcher does not get these values. What am I doing wrong?
class CurrencyFetcher implements CurrencyFetcherService {
final CurrencyRepo _currencyRepo;
StreamSubscription currencySubscription;
CurrencyFetcher(this._currencyRepo, this._preferencesService) {
_subscribeToCurrencies();
}
void _subscribeToCurrencies() {
currencySubscription = _currencyRepo
.getCurrenciesStream()
.listen((currencies) => _handleApiCurrencies);
}
Future<void> _handleApiCurrencies(List<ApiCurrency> apiCurrencies) async {
// implemetation
}
}
class CurrencyRepo {
final CurrencyApi _currencyApi;
final BehaviorSubject<List<ApiCurrency>> _currencySubject = BehaviorSubject.seeded([]);
Stream<List<ApiCurrency>> getCurrenciesStream() {
_updateCurrencies();
return _currencySubject.stream;
}
CurrencyRepo(this._currencyApi);
void _updateCurrencies() {
_currencyApi.getCurrencies().then((currencies) {
_currencySubject.add(currencies);
});
}
}
I have checked that the values are added to the stream after the CurrencyFetcher starts to listen. And I have checked that in the moment, when I add new value to the stream, it has a listener. The first time using RxDart, may someone help? :)
Inside the function _subscribeToCurrencies() try to write
_currencyRepo.currenciesStream.listen((currencies) {
_handleApiCurrencies(currencies);
});
instead of
_currencyRepo.currenciesStream.listen(_handleApiCurrencies);
So when writing UI in GTK it's generally preferrable to handle reading of files, etc. in an Async Method. things such as listboxes, are generally bound to a ListModel, the items in the ListBox updated in accordance with the items_changed signal.
So if I have some class, that implements ListModel, and has an add function, and some FileReader that holds a reference to said ListModel, and call add from an async function, how do i make that in essence triggering the items_changed and having GTK update accordingly?
I've tried list.items_changed.connect(message("Items changed!")); but it never triggers.
I saw this: How can one update GTK+ UI in Vala from a long operation without blocking the UI
but in this example, it's just the button label that is changed, no signal is actually triggered.
EDIT: (Codesample added at the request of #Michael Gratton
//Disclaimer: everything here is still very much a work in progress, and will, as soon as I'm confident that what I have is not total crap, be released under some GPL or other open license.
//Note: for the sake of readability, I adopted the C# naming convention for interfaces, namely, putting a capital 'I' in front of them, a decision i do not feel quite as confident in as I did earlier.
//Note: the calls to message(..) was put in here to help debugging
public class AsyncFileContext : Object{
private int64 offset;
private bool start_read;
private bool read_to_end;
private Factories.IVCardFactory factory;
private File file;
private FileMonitor monitor;
private Gee.Set<IVCard> vcard_buffer;
private IObservableSet<IVCard> _vCards;
public IObservableSet<IVCard> vCards {
owned get{
return this._vCards;
}
}
construct{
//We want to start fileops at the beginning of the file
this.offset = (int64)0;
this.start_read = true;
this.read_to_end = false;
this.vcard_buffer = new Gee.HashSet<IVCard>();
this.factory = new Factories.GenericVCardFactory();
}
public void add_vcard(IVCard card){
//TODO: implement
}
public AsyncFileContext(IObservableSet<IVCard> vcards, string path){
this._vCards = vcards;
this._vCards = IObservableSet.wrap_set<IVCard>(new Gee.HashSet<IVCard>());
this.file = File.new_for_path(path);
this.monitor = file.monitor_file(FileMonitorFlags.NONE, null);
message("1");
//TODO: add connect
this.monitor.changed.connect((file, otherfile, event) => {
if(event != FileMonitorEvent.DELETED){
bool changes_done = event == FileMonitorEvent.CHANGES_DONE_HINT;
Idle.add(() => {
read_file_async.begin(changes_done);
return false;
});
}
});
message("2");
//We don't know that changes are done yet
//TODO: Consider carefully how you want this to work when it is NOT called from an event
Idle.add(() => {
read_file_async.begin(false);
return false;
});
}
//Changes done should only be true if the FileMonitorEvent that triggers the call was CHANGES_DONE_HINT
private async void read_file_async(bool changes_done) throws IOError{
if(!this.start_read){
return;
}
this.start_read = false;
var dis = new DataInputStream(yield file.read_async());
message("3");
//If we've been reading this file, and there's then a change, we assume we need to continue where we let off
//TODO: assert that the offset isn't at the very end of the file, if so reset to 0 so we can reread the file
if(offset > 0){
dis.seek(offset, SeekType.SET);
}
string line;
int vcards_added = 0;
while((line = yield dis.read_line_async()) != null){
message("position: %s".printf(dis.tell().to_string()));
this.offset = dis.tell();
message("4");
message(line);
//if the line is empty, we want to jump to next line, and ignore the input here entirely
if(line.chomp().chug() == ""){
continue;
}
this.factory.add_line(line);
if(factory.vcard_ready){
message("creating...");
this.vcard_buffer.add(factory.create());
vcards_added++;
//If we've read-in and created an entire vcard, it's time to yield
message("Yielding...");
Idle.add(() => {
_vCards.add_all(vcard_buffer);
vcard_buffer.remove_all(_vCards);
return false;
});
Idle.add(read_file_async.callback);
yield;
message("Resuming");
}
}
//IF we expect there will be no more writing, or if we expect that we read ALL the vcards, and did not add any, it's time to go back and read through the whole thing again.
if(changes_done){ //|| vcards_added == 0){
this.offset = 0;
}
this.start_read = true;
}
}
//The main idea in this class is to just bind the IObservableCollection's item_added, item_removed and cleared signals to the items_changed of the ListModel. IObservableCollection is a class I have implemented that merely wraps Gee.Collection, it is unittested, and works as intended
public class VCardListModel : ListModel, Object{
private Gee.List<IVCard> vcard_list;
private IObservableCollection<IVCard> vcard_collection;
public VCardListModel(IObservableCollection<IVCard> vcard_collection){
this.vcard_collection = vcard_collection;
this.vcard_list = new Gee.ArrayList<IVCard>.wrap(vcard_collection.to_array());
this.vcard_collection.item_added.connect((vcard) => {
vcard_list.add(vcard);
int pos = vcard_list.index_of(vcard);
items_changed(pos, 0, 1);
});
this.vcard_collection.item_removed.connect((vcard) => {
int pos = vcard_list.index_of(vcard);
vcard_list.remove(vcard);
items_changed(pos, 1, 0);
});
this.vcard_collection.cleared.connect(() => {
items_changed(0, vcard_list.size, 0);
vcard_list.clear();
});
}
public Object? get_item(uint position){
if((vcard_list.size - 1) < position){
return null;
}
return this.vcard_list.get((int)position);
}
public Type get_item_type(){
return Type.from_name("VikingvCardIVCard");
}
public uint get_n_items(){
return (uint)this.vcard_list.size;
}
public Object? get_object(uint position){
return this.get_item((int)position);
}
}
//The IObservableCollection parsed to this classes constructor, is the one from the AsyncFileContext
public class ContactList : Gtk.ListBox{
private ListModel list_model;
public ContactList(IObservableCollection<IVCard> ivcards){
this.list_model = new VCardListModel(ivcards);
bind_model(this.list_model, create_row_func);
list_model.items_changed.connect(() => {
message("Items Changed!");
base.show_all();
});
}
private Gtk.Widget create_row_func(Object item){
return new ContactRow((IVCard)item);
}
}
Heres the way i 'solved' it.
I'm not particularly proud of this solution, but there are a couple of awful things about the Gtk ListBox, one of them being (and this might really be more of a ListModel issue) if the ListBox is bound to a ListModel, the ListBox will NOT be sortable by using the sort method, and to me at least, that is a dealbreaker. I've solved it by making a class which is basically a List wrapper, which has an 'added' signal and a 'remove' signal. Upon adding an element to the list, the added signal is then wired, so it will create a new Row object and add it to the list box. That way, data is control in a manner Similar to ListModel binding. I can not make it work without calling the ShowAll method though.
private IObservableCollection<IVCard> _ivcards;
public IObservableCollection<IVCard> ivcards {
get{
return _ivcards;
}
set{
this._ivcards = value;
foreach(var card in this._ivcards){
base.prepend(new ContactRow(card));
}
this._ivcards.item_added.connect((item) => {
base.add(new ContactRow(item));
base.show_all();
});
base.show_all();
}
}
Even though this is by no means the best code I've come up with, it works very well.