objectbox / objectbox-dart

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

ToMany class not working #539

Closed xOldeVx closed 10 months ago

xOldeVx commented 11 months ago

I'm trying to use ToMany class and it doesn't work, as you can see in the screenshot in line 46 a video inserted to objectbox, but in line 53 the video is not exist

11 22

I followed by this example https://youtu.be/AxYbdriXKI8?t=2157 and it's doesn't work in my side for some reason, i also followed by the official docs but no luck.

Here's the code

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:object_box/objectbox.g.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:objectbox/objectbox.dart';

late Store store;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Directory doc = await getApplicationDocumentsDirectory();
  store = await openStore(directory: p.join(doc.path, "videos"));

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final Box<Video> videoBox = store.box<Video>();
  final Box<Playlist> playlistBox = store.box<Playlist>();
  List<Playlist> playlistList = [];
  int counter = 0;

  void _incrementCounter() async {
    counter++;
    Playlist playlist = Playlist(counter.toString());
    Video video = Video('videoId-$counter', 'title-$counter');
    video.playlist.target = playlist;
    playlist.videos.add(video);
    await playlistBox.put(playlist);
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    playlistList = playlistBox.getAll();
    return Scaffold(
      appBar: AppBar(
        title: Text('OBJECT BOX!'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Playlist'),
            plWidget(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget plWidget() {
    return ListView(
      shrinkWrap: true,
      children: List.generate(
        playlistList.length,
        (i) {
          if (playlistList.isEmpty) {
            return Text('No playlists');
          }
          Playlist playlist = playlistList[i];
          return Center(
            child: Text(
                'Playlist: ${playlist.title}, video: ${playlist.videos.isEmpty ? 'Empty' : playlist.videos.first.title}'),
          );
        },
      ),
    );
  }
}

@Entity()
class Playlist {
  @Id()
  int id = 0;
  @Index()
  final String title;

  @Backlink()
  final List<Video> videos = ToMany<Video>();

  Playlist(this.title);
}

@Entity()
class Video {
  @Id()
  int id = 0;
  @Index()
  final String videoId;
  final String title;

  final playlist = ToOne<Playlist>();

  Video(this.videoId, this.title);
}

objectbox: ^2.0.0 Flutter channel stable, 3.7.2

greenrobot-team commented 11 months ago

The type of a to-many relation field must be ToMany, it can not be List.

E.g. change your Playlist class to this:

@Entity()
class Playlist {
  @Id()
  int id = 0;
  @Index()
  final String title;

  @Backlink()
  final videos = ToMany<Video>(); // <---

  Playlist(this.title);
}

Also note that you only need to add the Video on "one side", e.g. this is enough:

  void _incrementCounter() async {
    counter++;
    Playlist playlist = Playlist(counter.toString());
    Video video = Video('videoId-$counter', 'title-$counter');
    // video.playlist.target = playlist;
    playlist.videos.add(video);
    playlistBox.put(playlist);
    setState(() {});
  }

I would also recommend to use putAsync and to listen to a query stream for your list view.

Let me know if this resolves your issue. If there is no response it will be automatically closed in about 2 weeks.

xOldeVx commented 11 months ago

@greenrobot-team It's working thanks!! I should have just change the Playlist<Video> type to final

Now I encounter another problem, I save the database in different files because some of them are the same object type but with a different meaning (for example, videos history and favorite are the same object - video), and now I encounter another problem, i'm getting error: Invalid argument(s): object put failed: ID is higher or equal to internal ID sequence: 1 (vs. 1). Use ID 0 (zero) to insert new entities. (OBX_ERROR code 10002)

Here's example of code, just click on "Add me.." and then on "Watch me.." button

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:object_box/objectbox.g.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:objectbox/objectbox.dart';

late Store plStore;
late Store videosStore;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Directory doc = await getApplicationDocumentsDirectory();
  plStore = await openStore(directory: p.join(doc.path, "pl"));
  videosStore = await openStore(directory: p.join(doc.path, "vd"));

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final Box<Video> historyBox = videosStore.box<Video>();
  final Box<Playlist> playlistBox = plStore.box<Playlist>();

  final Video video = Video('123abc', 'A video title');

  void addVideoToPlaylist() {
    Playlist playlist = Playlist('My playlist');
    // video.playlist.target = playlist;
    playlist.videos.add(video);
    playlistBox.put(playlist);
    setState(() {});
  }

  void addToHistory() {
    historyBox.put(video);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('OBJECT BOX!'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('A Video'),
            ElevatedButton(onPressed: () => addVideoToPlaylist(), child: Text('Add me to playlist')),
            ElevatedButton(onPressed: () => addToHistory(), child: Text('Watch me now (NOT FROM PLAYLIST!)')),
          ],
        ),
      ),
    );
  }
}

@Entity()
class Playlist {
  @Id()
  int id = 0;
  @Index()
  final String title;

  final videos = ToMany<Video>();

  Playlist(this.title);
}

@Entity()
class Video {
  @Id()
  int id = 0;
  @Index()
  final String videoId;
  final String title;

  Video(this.videoId, this.title);
}
greenrobot-team commented 11 months ago

First of all, you can use the same store and have multiple boxes in it (e.g. you can store both Playlist and Video in one store).

In your example the Video is stored already in plStore when calling playlistBox.put(playlist);.

If you really want two separate stores (database files), these do not share any data. The issue is that with playlistBox.put(playlist); the video will get assigned an ID from the plStore. So when trying to put the video to videosStore it does not know about it. You need to insert with ID 0 again, or if really needed enabled self-assigned IDs.

xOldeVx commented 11 months ago

@greenrobot-team

the video will get assigned an ID from the plStore. So when trying to put the video to videosStore it does not know about it. You need to insert with ID 0 again, or if really needed enabled self-assigned IDs

Sorry, I didn't really understand how to implement it, can you provide a sample please?

xOldeVx commented 11 months ago

You can provide please code sample based on my code how to solve it, it's a big help to me to continue with the project, thanks

greenrobot commented 11 months ago

While we would like to support everybody with their individual goals, our resources are limited. Maybe ChatGPT can help with your individual code and your individual goals?

xOldeVx commented 11 months ago

I'm really not used to asking for help but with this package I got a little lost, one of the things I don't understand for example is that when I add a video to the playlist db the ID is 1, which is perfectly fine, and when I then add the video to the history db the video get ID:1 which is also fine because it's a separate database and should manage separate ids. So why is there a crash here?

greenrobot-team commented 11 months ago

Maybe looking at https://docs.objectbox.io/getting-started#object-ids will help clear things up.

github-actions[bot] commented 10 months ago

Without additional information, we are unfortunately not sure how to resolve this issue. Therefore this issue has been automatically closed. Feel free to comment with additional details and we can re-open this issue.