Core Data for Android

Overview

NexusData

Core Data for Android

NexusData is an object graph and persistence framework for Android. It allows for organizing and managing relational data and serializing it to SQLite or custom stores. The data can be accessed or modified using higher level objects representing entities and their relationships. NexusData manages all the objects in the persistence store, tracks changes, and maintains consistency in relationships.

Essentially, it brings Core Data functionality from iOS to Android. However, the library is not intended to be a straight port of Core Data. Instead, it aims to leverage Core Data's concepts, while having the flexibility to evolve independently.

NexusData is not an ORM in that it's more higher-level and is not tied to a specific storage engine. The query interface is oblivious to the underlying persistence store.

NexusData supports Android API 10+. This library follows semantic versioning. Note that this library is still active in development, and is missing a lot of features. New releases might introduce interface-breaking changes, which will be indicated in the changelog. NexusData 1.0.0 will be the first stable release.

Samples

For sample projects that use NexusData, browse the samples directory.

Features

  • Change tracking and management of objects.
  • Relationship maintenance by automatically propagating related changes to maintain consistency.
  • Support for one-to-one and one-to-many relationships.
  • Lazy loading of the object graph to reduce memory overhead.
  • Flexible query interface that is independent of the underlying storage engine.
  • Model generator that generates java classes from the model.
  • Entity inheritence.
  • Support for atomic and incremental persistence stores.
  • Extensible to different persistence storage. Currently, two storage engines are provided out of the box:
    • In-memory
    • SQLite
  • Built-in support for basic attribute types:
    • Short / Integer / Long
    • Boolean
    • Float / Double
    • String
    • Enum
    • Date

Limitations

The framework is constantly being improved and new features are being implemented. Since it's very early stage, it currently has some limitations:

  • Undo/Redo is not supported.
  • Many-to-many relationships are not supported yet.
  • Schema migrations are not supported yet.
  • Query syntax is currently limited to comparisons and boolean logic. Operations like aggregations and joining are not supported yet.
  • Framework is not yet optimized for large data sets in terms of performance and memory. This is due to the early development of the project and will be improved over time.
  • Custom data types are not supported yet.

Apps Using NexusData

Do you have an app that's utilizing NexusData? Let me know and I'll add a link to it here!

How to Add NexusData to Your Project

There are multiple ways to include your project, depending on your build environment:

Gradle

Add the following dependency to your build.gradle file for your project:

dependencies {
  compile 'com.github.dkharrat.nexusdata:nexusdata:0.2.1'
}

Maven

Add the following dependency to your pom.xml file for your project:

<dependency>
    <groupId>com.github.dkharrat.nexusdata</groupId>
    <artifactId>nexusdata</artifactId>
    <version>0.2.1</version>
    <type>jar</type>
</dependency>

Android Studio or IntelliJ 13+

Add the appropriate dependency in your build.gradle file and refresh your project.

Eclipse

TBD

How to Get Started

For a complete example of how NexusData can be used, please browse through the samples included with the project.

Defining the model

The model is used to provide NexusData with information about the entities and their properties. A model can be defined either programmatically or via JSON file. Here's an example of a JSON-based model file for a ToDo app:

todo.model.json:

{
  "metaVersion": 1,
  "model": {
    "name": "Todo",
    "version": 3,
    "packageName": "org.example.todo",
    "entities": [{
      "name": "Task",
      "enums": [{
        "name": "Priority",
        "values": ["HIGH", "MEDIUM", "LOW"]
      }],
      "attributes": [{
        "name": "title",
        "type": "String"
      }, {
        "name": "notes",
        "type": "String"
      }, {
        "name": "dueBy",
        "type": "Date"
      }, {
         "name": "completed",
         "type": "Bool",
         "required": true,
         "default": false
       }, {
         "name": "priority",
         "type": "Priority"
       }],
      "relationships": [{
        "name": "assignedTo",
        "destinationEntity": "User",
        "inverseName": "tasks",
        "toMany": false
      }]
    }, {
      "name": "User",
      "attributes": [{
        "name": "name",
        "type": "String"
      }],
      "relationships": [{
        "name": "tasks",
        "destinationEntity": "Task",
        "inverseName": "assignedTo",
        "toMany": true
      }]
    }]
  }
}

This model (named "Todo") defines two entities: Task and User. A Task belongs to a User, and a User has many Tasks. Also, each entity has some attributes.

Generating classes from a model file

NexusData comes with a Model Generator that allows you to generate an appropriate class for each entity. Though using the generator is not necessary to use NexusData, the Model Generator reduces the need to write a lot of repetitive and boilerplate code for each entity (getter, setters, etc.). If you choose not to use the Model Generator, you may either create the classes yourself or use ManagedObject directly.

The model generator source code is located under the modelgen directory. To generate the appropriate classes from the above model file, run this command:

gradle :modelgen:run -Pargs="-f /path/to/model/todo.model.json -O /path/to/output/src/main/java"

This will parse the todo.model.json file and generate the corresponding classes in the src/main/java/org/example/todo directory. The output of the generator will look something like this:

03:43:41.970 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Setting up model generator
03:43:42.028 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Parsing model file 'todo.model.json'
03:43:42.098 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Generating class files for 'Todo' model (version 1)
03:43:42.152 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Generating class Task.java
03:43:42.161 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Generating class _Task.java
03:43:42.184 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Generating class User.java
03:43:42.184 [main] INFO  c.g.d.n.modelgen.ModelGenerator - Generating class _User.java

For each entity, two classes will be generated. For example, for the Task entity, Task.java and _Task.java are generated. The _Task.java file contains all the accessors, enums, and relationships based on the model file. The other file, Task.java, is an empty class that inherits from _Task. The Task class can be used to define any custom code or derived properties. The reason for creating two classes is to allow you to re-generate the entity classes if the model changes, while maintaining any custom code associated with the entity in a separate file. The generator will not overwrite your custom class (e.g. Task.java in this example) if it already exists, but it will overwrite the base class (e.g. _Task.java).

Here's how the generated files look like for the Task entity:

_Task.java:

// THIS IS AN AUTO-GENERATED CLASS FILE. DO NOT EDIT DIRECTLY.

package org.example.todo;

import java.util.Date;
import com.github.dkharrat.nexusdata.core.ManagedObject;

class _Task extends ManagedObject {

    public interface Property {
        String TITLE = "title";
        String NOTES = "notes";
        String DUE_BY = "dueBy";
        String COMPLETED = "completed";
        String PRIORITY = "priority";
        String ASSIGNED_TO = "assignedTo";
    }

    public enum Priority {
        HIGH,
        MEDIUM,
        LOW,
    }

    public String getTitle() {
        return (String)getValue(Property.TITLE);
    }

    public void setTitle(String title) {
        setValue(Property.TITLE, title);
    }

    public String getNotes() {
        return (String)getValue(Property.NOTES);
    }

    public void setNotes(String notes) {
        setValue(Property.NOTES, notes);
    }

    public Date getDueBy() {
        return (Date)getValue(Property.DUE_BY);
    }

    public void setDueBy(Date dueBy) {
        setValue(Property.DUE_BY, dueBy);
    }

    public boolean isCompleted() {
        return (Boolean)getValue(Property.COMPLETED);
    }

    public void setCompleted(boolean completed) {
        setValue(Property.COMPLETED, completed);
    }

    public Priority getPriority() {
        return (Priority)getValue(Property.PRIORITY);
    }

    public void setPriority(Priority priority) {
        setValue(Property.PRIORITY, priority);
    }


    public User getAssignedTo() {
        return (User)getValue(Property.ASSIGNED_TO);
    }

    public void setAssignedTo(User assignedTo) {
        setValue(Property.ASSIGNED_TO, assignedTo);
    }
}

Task.java:

package org.example.todo;

public class Task extends _Task {
    public Task() {
    }
}

Initializing NexusData Stack

To setup NexusData in your application, you'll need three main parts: ObjectModel, PersistentStoreCoordinator, and a ObjectContext. These can typically be initialized once at startup and used throughout the lifetime of the application.

// create an ObjectModel that describes the meta model
ObjectModel model = new ObjectModel(app.getAssets().open("todo.model.json"));

// create the persistent store coordinator and its associated store
PersistentStoreCoordinator storeCoordinator = new PersistentStoreCoordinator(model);
Context ctx = getApplicationContext(); // the Android context
PersistentStore cacheStore = new AndroidSqlPersistentStore(ctx, ctx.getDatabasePath("todo"));
storeCoordinator.addStore(cacheStore);

// create an ObjectContext that will be used to retrieve or save our objects
ObjectContext mainObjectContext = new ObjectContext(storeCoordinator);

Creating/Updating Objects

ObjectContext objCtx = getMainObjectContext();
Task task1 = objCtx.newObject(Task.class);
task1.setTitle("Get groceries");

Task task2 = objCtx.newObject(Task.class);
task2.setTitle("File taxes");

objCtx.save();

Deleting Objects

objCtx.delete(task1);
objCtx.save();

Querying All Objects of Specific Type

List<Task> tasks = objCtx.findAll(Task.class)

Querying Objects Satisfying a Predicate

For example, to query all Tasks that are complete:

FetchRequest<Task> fetchRequest = objCtx.newFetchRequestBuilder(Task.class)
    .predicate("completed == true")
    .build();
List<Task> tasks = objCtx.executeFetchOperation(fetchRequest);

Use ObjectContext and ManagedObjects in multiple threads

Similar to Core Data, ManagedObject and ObjectContext are not thread-safe, and therefore, should not be used in other threads. Each thread must use its own instance of ObjectContext. To pass objects between multiple threads, pass the object's ObjectID to the other thread, which can then retrieve the object from it's own ObjectContext.

To synchronize multiple ObjectContext with any changes, register a listener and then merge the changes when receiving a notification, as follows:

ObjectContextNotifier.registerListener(new ObjectContextNotifier.DefaultObjectContextListener() {
    @Override public void onPostSave(ObjectContext context, ChangedObjectsSet changedObjects) {
        // ensure that the notification we just got is not from our own context, and that it's from a context using a
        // persistence store that our context is also using.
        if (context != mainObjectContext && context.getPersistentStoreCoordinator() == mainObjectContext.getPersistentStoreCoordinator()) {
            mainObjectContext.mergeChangesFromSaveNotification(changedObjects);
        }
    }
});

Documentation

See the current Javadoc.

Contributing

Contributions via pull requests are welcome! For suggestions, feedback, or feature requests, please submit an issue.

Author

Dia Kharrat - [email protected]
Twitter: http://twitter.com/dkharrat

License

Copyright 2014 Dia Kharrat

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • PredicateBuilder

    PredicateBuilder

    I create a Predicate: Predicate predicate = PredicateBuilder.parse("testId == 53c7eaf909c7d69602013c68"); and System.out.println(predicate) return (testId EQUAL 53).

    opened by sawcioo 6
  • [BUG] PredicateToSQL

    [BUG] PredicateToSQL

    Hello, i create predicate like this: PredicateBuilder.parse("createDate != null"). I gets java.lang.NullPointerException at com.github.dkharrat.nexusdata.store.DatabaseQueryService$QueryBuilder.visit(PredicateToSQL.java:85) at com.github.dkharrat.nexusdata.store.DatabaseQueryService$QueryBuilder.visit(PredicateToSQL.java:166) at com.github.dkharrat.nexusdata.store.DatabaseQueryService$QueryBuilder.visit(PredicateToSQL.java:137) at com.github.dkharrat.nexusdata.store.DatabaseQueryService$QueryBuilder.visit(PredicateToSQL.java:156) at com.github.dkharrat.nexusdata.store.DatabaseQueryService$QueryBuilder.visit(PredicateToSQL.java:116) at com.github.dkharrat.nexusdata.store.DatabaseQueryService$QueryBuilder.visit(PredicateToSQL.java:154) at com.github.dkharrat.nexusdata.store.DatabaseQueryService.buildQuery(PredicateToSQL.java:62) at com.github.dkharrat.nexusdata.store.DatabaseQueryService.query(PredicateToSQL.java:41) at com.github.dkharrat.nexusdata.store.AndroidSqlPersistentStore.executeFetchRequest(AndroidSqlPersistentStore.java:85) at com.github.dkharrat.nexusdata.core.ObjectContext.executeFetchOperation(ObjectContext.java:120)

    opened by sawcioo 3
  • Parent Entity supported?

    Parent Entity supported?

    Is Parent Entity supported. Tried to set superEntityName but it is not processed?!

    "entities": [{
          "name": "Record",
          "attributes": [{
            "name": "id",
            "type": "String"
          }]
        }, {
          "name": "Item",
          "superEntityName" : "Record",
    

    In ObjectModelJsonParser there is a static class called Entity. It has superEntity field. Strange though from Json it does not read up value.

        static class Entity {
            String name;
            @SerializedName("extends") String superEntityName;
            List<Attribute> attributes;
            List<Relationship> relationships;
            List<EnumProperty> enums;
        }
    
    opened by j4nos 2
  • gradle not found when trying to generate model files

    gradle not found when trying to generate model files

    Where should I put in the following command on a Mac, in terminal? Gradle shuld be preinstalled on my Mac?

    gradle :modelgen:run -Pargs="-f /path/to/model/todo.model.json -O /path/to/output/src/main/java"

    I do not see gradle script in the project folder.

    Kukodas-MacBook-Air:MovieBuffs3 kukodajanos$ ls MovieBuffs3.iml gradle local.properties app gradle.properties settings.gradle build gradlew build.gradle gradlew.bat Kukodas-MacBook-Air:MovieBuffs3 kukodajanos$ gradle -bash: gradle: command not found

    opened by j4nos 2
  • FetchRequest does not return

    FetchRequest does not return

    I have an ordinary FetchRequest as tutorial write, but it does not return:

    FetchRequest<Record> fetchRequest = c.newFetchRequestBuilder(Record.class)
                    .predicate("id == " + id)
                    .build();
    List<Record> records = c.executeFetchOperation(fetchRequest);
    

    id is a string. Lines are executed from a serial queue. context was created on the same thread where call is now. Debugger does not reach c.executeFetchOperation line. @dkharrat do you have any idea what is wrong?

    opened by j4nos 1
  • Where is pom.xml located?

    Where is pom.xml located?

    I am a bit newbie in Android development. Have more experience in iOS development. Have a Mac and Android Studio. How can I 'Add the following dependency to your pom.xml file for your project:' ?I do not find pom.xml in my project I just created. Do I have to install first Maven? Or is it coming with Android Studio?

    opened by j4nos 1
  • keypath in predicate?

    keypath in predicate?

    Am I right that predicate does not accept keypath, only key? i.e. will not work?

    "item.id == "tt2707408" AND userSetting.id == "625345927616512""

    opened by j4nos 0
  • SUBQUERY in NexusData

    SUBQUERY in NexusData

    NexusData does have SUBQUERY language element from Core Data, like this:

    let fr2: NSFetchRequest<Item> = Item.fetchRequest()
    fr2.predicate = NSPredicate(format: "SUBQUERY(ratings, $r, ($r.userSetting.id = %@ AND $r.scale > 0)).@count = 0 AND SUBQUERY(ratings, $r, ($r.userSetting.id != %@ AND $r.scale > 0)).@count > 0", loggedInUserID!, loggedInUserID!)
    
    opened by j4nos 0
  • NSMergeByPropertyObjectTrumpMergePolicy

    NSMergeByPropertyObjectTrumpMergePolicy

    Hi!

    In CoreData it is possible to set mergePolicy.

    context!.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy.

    Sometimes upserting managedObjects is quite frequent and queriing by ID and upsetting mo would create two records. But NSMergeByPropertyObjectTrumpMergePolicy prevent it. Is it possible somehow in nexus data?

    opened by j4nos 0
  • Orderedset instead of Set?

    Orderedset instead of Set?

    Is it possible to get back ordered set instead of set in nexus data? Implement i.e. _User class differently, than recently?

     @SuppressWarnings("unchecked")
        public Set<Task> getTasks() {
            return (Set<Task>)getValue(Property.TASKS);
        }
    

    BTW do you now why is no "ordered" descriptor possible to set in model scheme?

    opened by j4nos 1
  • What is wrong with predicate?

    What is wrong with predicate?

    demand != null && demand.satisfied == false && gender == "male"

    I have the following predicate, and app crashes. Why?

    I guess constants are OK, based on this: lexerGrammar.add("(\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\")|\\d+|true|false|null", TokenType.CONSTANT);

    comparators too:

     lexerGrammar.add("\\(",     TokenType.OPEN_PAREN);
            lexerGrammar.add("\\)",     TokenType.CLOSE_PAREN);
            lexerGrammar.add("&&",      TokenType.AND);
            lexerGrammar.add("\\|\\|",  TokenType.OR);
            lexerGrammar.add("==",      TokenType.EQUAL);
            lexerGrammar.add("!=",      TokenType.NOT_EQUAL);
            lexerGrammar.add(">=",      TokenType.GREATER_THAN_OR_EQUAL);
            lexerGrammar.add("<=",      TokenType.LESS_THAN_OR_EQUAL);
            lexerGrammar.add(">",       TokenType.GREATER_THAN);
            lexerGrammar.add("<",       TokenType.LESS_THAN);
            lexerGrammar.add("(\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\")|\\d+|true|false|null",       TokenType.CONSTANT);
            lexerGrammar.add("[a-zA-Z][a-zA-Z0-9_]*",   TokenType.FIELD_NAME);
    
    opened by j4nos 2
  • Is weak-typing supported when modifying record?

    Is weak-typing supported when modifying record?

    In iOS it is possible to modify managedObject like this: mo.setValue(obj, forKey: "someAttribute")

    Is it possible pass argument i.e. string and not class, pass argument without static type-check?

    Or only by passing class as argument? Task task1 = objCtx.newObject(Task.class);

    http://stackoverflow.com/questions/40890602/get-dao-with-weak-typing-in-ormlite

    opened by j4nos 1
  • why it's

    why it's "not yet optimized for large data sets"

    First thanks for this project, it's awesome! So I was trying to figure the reason why you said the above in the Limitations list. From what I can tell the ManagedObjects registered from fetches are never unregistered, and their corresponding entry in the entityCache is never removed, thus the memory will never be freed for objects that are queried but not deleted. I would imagine that CoreData on the Mac makes use of reference counting to decide when it is suitable to release a ManagedObject from the cache. It likely monitors if any of the objects have a reference count of 1 and if so removes them from the context's object array, resulting in a release. I had a quick look at it seems Java doesn't have any mechanism to detect if an object is referenced by any other making this quite a challenging problem to solve elegantly. Since the feature is likely a long way off, as a work around in the mean time would you consider making a temporary method that both unregisters the object and also removes it from the entityCache? Perhaps make unregisterObject public then in ManagedObject setManagedObjectContext it could check if its null and if so it could get the cacheNode and remove it? My reason for this method would be although we don't have a way to track when the right time is to free an object, at least having the method to do it would be a good start.

    opened by malhal 4
Owner
Dia Kharrat
Dia Kharrat
Implementation of MVVM , Live Data and Room DAO for a robust materialistic design

Someday App to manage Weekly tasks Because who needs to remind you every week to do Samething Preview Main Layout Light Dark Main Layout (Expanded) Li

Anshul Saraf 2 May 13, 2021
A tool to convert & query Apache Calcite data sources as GraphQL API's

Apache Calcite <-> Distributed, Federated GraphQL API Apache Calcite <-> Distributed, Federated GraphQL API Goals Roadmap and Current Progress The Roa

Gavin Ray 19 Nov 22, 2022
Active record style SQLite persistence for Android

ActiveAndroid ActiveAndroid is an active record style ORM (object relational mapper). What does that mean exactly? Well, ActiveAndroid allows you to s

Michael Pardo 4.7k Dec 29, 2022
A blazing fast, powerful, and very simple ORM android database library that writes database code for you.

README DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to gener

Andrew Grosner 4.9k Dec 30, 2022
greenDAO is a light & fast ORM solution for Android that maps objects to SQLite databases.

Check out ObjectBox Check out our new mobile database ObjectBox (GitHub repo). ObjectBox is a superfast object-oriented database with strong relation

Markus Junginger 12.6k Jan 3, 2023
Object-relational mapping for Android

RushOrm Object-relational mapping for Android RushOrm replaces the need for SQL by mapping java classes to SQL tables. What is the aim? The aim is to

Stuart Campbell 172 Nov 11, 2022
Insanely easy way to work with Android Database.

Sugar ORM Insanely easy way to work with Android databases. Official documentation can be found here - Check some examples below. The example applicat

null 2.6k Dec 16, 2022
Android ORM

Shillelagh Shillelagh is an sqlite library. It was built to make life easier. The entire library was built around simplicity when using sqlite in Andr

Andrew Reitz 49 Sep 11, 2020
Compile-time active record ORM for Android

Ollie Compile-time active record ORM for Android. Multiple mapping methods. SQLiteDatabase-like interface (QueryUtils.java). Lightweight query builder

Michael Pardo 423 Dec 30, 2022
requery - modern SQL based query & persistence for Java / Kotlin / Android

A light but powerful object mapping and SQL generator for Java/Kotlin/Android with RxJava and Java 8 support. Easily map to or create databases, perfo

requery 3.1k Dec 29, 2022
A blazing fast, powerful, and very simple ORM android database library that writes database code for you.

README DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to gener

Andrew Grosner 4.9k Dec 30, 2022
A simple NoSQL client for Android. Meant as a document store using key/value pairs and some rudimentary querying. Useful for avoiding the hassle of SQL code.

SimpleNoSQL A simple NoSQL client for Android. If you ever wanted to just save some data but didn't really want to worry about where it was going to b

Colin Miller 389 Sep 25, 2022
An Android library that makes developers use SQLite database extremely easy.

LitePal for Android 中文文档 LitePal is an open source Android library that allows developers to use SQLite database extremely easy. You can finish most o

Lin Guo 7.9k Dec 31, 2022
Active record style SQLite persistence for Android

ActiveAndroid ActiveAndroid is an active record style ORM (object relational mapper). What does that mean exactly? Well, ActiveAndroid allows you to s

Michael Pardo 4.7k Dec 29, 2022
Insanely easy way to work with Android Database.

Sugar ORM Insanely easy way to work with Android databases. Official documentation can be found here - Check some examples below. The example applicat

null 2.6k Jan 9, 2023
An Android helper class to manage database creation and version management using an application's raw asset files

THIS PROJECT IS NO LONGER MAINTAINED Android SQLiteAssetHelper An Android helper class to manage database creation and version management using an app

Jeff Gilfelt 2.2k Dec 23, 2022
Android SQLite API based on SQLCipher

Download Source and Binaries The latest AAR binary package information can be here, the source can be found here. Compatibility SQLCipher for Android

SQLCipher 2.6k Dec 31, 2022
SquiDB is a SQLite database library for Android and iOS

Most ongoing development is currently taking place on the dev_4.0 branch. Click here to see the latest changes and try out the 4.0 beta. Introducing S

Yahoo 1.3k Dec 26, 2022
Sprinkles is a boiler-plate-reduction-library for dealing with databases in android applications

Sprinkles Sprinkles is a boiler-plate-reduction-library for dealing with databases in android applications. Some would call it a kind of ORM but I don

Emil Sjölander 781 Nov 28, 2022