objectbox / objectbox-dart

Flutter database for super-fast Dart object persistence
https://docs.objectbox.io/getting-started
Apache License 2.0
1.04k stars 120 forks source link

error passing parameters #413

Closed rodriger06 closed 2 years ago

rodriger06 commented 2 years ago

Hi, i am having an issue passing parameters to my query. I have the following object box class


import 'package:finsec/features/dashboard/data/datasources/InheritedProvider.dart';
import 'package:finsec/features/income/data/models/Income.dart';

import '../../../../objectbox.g.dart';
import 'dart:async';

class ObjectBox {
  /// The Store of this app.
  late final Store store;
  late final Box<Income> incomeBox;

  /// A stream of all notes ordered by date.
  late final Stream<Query<Income>> incomeQueryStream;
  late final qBuilder;
  ObjectBox._create(this.store) {
    // Add any additional setup code, e.g. build queries.
    incomeBox = Box<Income>(store);

    qBuilder = incomeBox.query((Income_.category.equals(''))).build;
    incomeQueryStream = qBuilder.watch(triggerImmediately: true);
    if (incomeBox.isEmpty()) {
      print('EMPTY');
    }
  }

  /// Create an instance of ObjectBox to use throughout the app.
  static Future<ObjectBox> create() async {
    // Future<Store> openStore() {...} is defined in the generated objectbox.g.dart
    final store = await openStore();
    return ObjectBox._create(store);
  }
}

I want to pass a parameter from another class to this line

   qBuilder = incomeBox.query((Income_.category.equals(''))).build;

in the main class, i have the following:

Future<void> main() async {
  // This is required so ObjectBox can get the application directory
  // to store the database in.
  WidgetsFlutterBinding.ensureInitialized();

  objectBox = await ObjectBox.create();

  runApp(new MyHomePage( initialDate: DateTime.now()));
  //runApp(new MyHomePage( initialDate: DateTime.now()));
  //runApp(MyApp());
}

in another class i have the following code(this is partial code:


class IncomeTransactionState extends State<IncomeTransaction> {
  final _listController = StreamController<List<Income>>(sync: true);

  @override
  void initState() {
    super.initState();
    setState(() {
        objectBox.qBuilder.param(Income_.category).value = 'Salary';
      _listController.addStream(objectBox.incomeQueryStream.map((q) => q.find()));
    });

  }
 @override
  void dispose() {
    _listController.close();
    super.dispose();
  }

Widget incomeSummaryStream() {
    return StreamBuilder<List<Income>>(
      stream: _listController.stream,,
      builder: (context, snapshot) {
        final activeIncomes = snapshot.data;

        if (!snapshot.hasData || activeIncomes!.length == 0) {
          return  Align(
              alignment: Alignment.center,
              child: noData()
          );
        }
        return itemList(activeIncomes);
      },
    );
  }

I get the following error when i run my code:

E/flutter ( 9886): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: Closure call with mismatched arguments: function 'watch'
E/flutter ( 9886): Receiver: Closure: () => Query<Income> from Function 'build':.
E/flutter ( 9886): Tried calling: watch(triggerImmediately: true)
E/flutter ( 9886): Found: watch() => Query<X0>
E/flutter ( 9886): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:68:5)
E/flutter ( 9886): #1      new ObjectBox._create (package:finsec/features/common/data/datasources/ObjectBox.dart:24:34)
E/flutter ( 9886): #2      ObjectBox.create (package:finsec/features/common/data/datasources/ObjectBox.dart:35:22)
E/flutter ( 9886): <asynchronous suspension>
E/flutter ( 9886): #3      main (package:finsec/main.dart:36:15)
E/flutter ( 9886): <asynchronous suspension>
E/flutter ( 9886): 

what am i doing wrong? I want to be able to pass parameters from another class into my objectbox class which contain the query.
My app will have a drop down where users will select a value. I want my class to pass parameters to the query everytime the user select a value and the data should be display in the screen.

can someone help me modify my code so that parameters are passed without causing error? thanks in advance

greenrobot-team commented 2 years ago
qBuilder = incomeBox.query((Income_.category.equals(''))).build;
incomeQueryStream = qBuilder.watch(triggerImmediately: true);

The above code doesn't compile. This does:

qBuilder = incomeBox.query(Income_.category.equals(''));
incomeQueryStream = qBuilder.watch(triggerImmediately: true);

And to supply the parameter you can modify the query before running it:

_listController.addStream(objectbox.incomeQueryStream.map((q) {
  q.param(Income_.category).value = 'Salary';
  return q.find();
}));
rodriger06 commented 2 years ago

Thank you so much. This works perfect. However, I encounter an issue when passing integer parameters. for string parameters, the following line will work fine:

qBuilder = incomeBox.query((Income_.category.equals(''))).build;

however, if i try to pass an integer by changing the column name such as monthNumber

qBuilder = incomeBox.query((Income_.monthNumber.equals(''))).build;

I get the following error

the argument type 'String' can't be assigned to the parameter type 'int'. (Documentation) 

can you show me how to fix the line of code so that i can also pass integer/number parameters? thanks in advance

greenrobot-team commented 2 years ago

Instead of an empty string '' pass a number e.g. 0?

rodriger06 commented 2 years ago

Thank you for your help. It works for both integer and string. however, when I pass the parameters at run time, the first set of parameters works fine and then the second call gets me the following error:

The following StateError was thrown building StreamBuilder<dynamic>(state: _StreamBuilderBaseState<dynamic, AsyncSnapshot<dynamic>>#478b1):
Bad state: Stream has already been listened to.

here is summary of what I am doing. my main screen has 3 buttons. each button will call class IncomeTransaction(mentioned above) but each button will pass a different parameter. the first button will pass 1 as a parameter to class IncomeTransaction and display output of the query. the second button will pass 2, and third button will pass 3. When I run the app the first time, I click button 1 and the value of 1 is pass to class IncomeTransaction which will run the following query based on the parameter

_listController.addStream(objectbox.incomeQueryStream.map((q) {
  q.param(Income_.monthNumber).value =  paramValue;
  return q.find();
}));

this works fine. now when I go back to the main screen, i click on button two so that value 2 is pass as a parameter and the query runs based on that value. however, i get error: Bad state: Stream has already been listened to. i have the following function in my class

 void dispose() {
    _listController.close();
    super.dispose();
  }

I changed the following line from:

  final _listController = StreamController<List<Income>>(sync: true);

to

  final _listController = StreamController<List<Income>>.broadcast(sync: true);

but it didnt work. I am not sure what I am doing wrong. All I want is to call IncomeTransaction and pass different values so that the _listController gets executed and display the query result for any number of parameters been passed without any errors. I want the stream to be listening for new data from the query based on the parameters been passed and display the data. can anybody help figure out how to fix my code ? thanks in advance

greenrobot-team commented 2 years ago

@rodriger06 This is about how to use StreamBuilder. I suggest to look at its documentation and relevant examples.

I only have time to help you with ObjectBox related issues, sorry.

greenrobot-team commented 2 years ago

Closing this issue due to inactivity. :zzz: Feel free to comment with more details or submit a new issue.