Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
I am working on an Android project and am currently trying to figure out how to deserialize some JSON from our APIs that includes reference cycles into an object graph, which I can then manipulate and store in a database. Let me give an example:
{
"id": "24",
"name": "Bob",
"friends": [
{
"id": "13",
"name": "Alice",
"friends": [
{
"id": "24" // and we have a circular reference
}
]
}
]
}
Here, a person object called Bob is friends with person Alice, and Alice is in turn friends with Bob. Since the relationship is recursive, Alice’s friends relationship to Bob is not realized as a full person object anymore but only his id is provided.
What tools do you use to perform the above mentioned steps? I tried to implement the object mapping part with Jackson but failed to find a solution for the cycle requirement. I found an ongoing discussion about this topic that mentions JSOG which might be helpful, but our APIs are fixed and not JSOG compliant.
Basically what I am looking for is something like RestKit (iOS framework) for Android.
Once API is fixed, I'd implement it in this manner:
From DB perspective, I'd have 2 tables - UserTable and RelationsTable to keep all edges of your friends graph:
I.e. the idea is to keep Users in the one table and their relations in Relations table. It allows also to add some extra logic on top of it later (for example, user hides his connection or blocks someone, etc. - any possible edges of the graph). Also, it allows to mitigate issues with circular references.
As a framework to retrieve data from service & parse jsons, I'd use Retrofit.
First, I'd define UserBase and User classes:
public class UserBase {
public string id;
}
public final class User extends UserBase {
public string name;
public List<UserBase> friends;
// user's "real" friends, not just ids, fills from SQLite
public List<User> userFriends;
}
where, as you can see, friends is a list of UserBase objects for Retrofit to parse the object from JSON and userFriends - the list, which we'll fill from SQLite manually in further steps.
Now, let's define some help-classes to operate with DBs:
public interface Dao<TItem> {
void add(List<TItem> items);
void removeAll();
List<TItem> getAll();
}
....
public abstract class AbstractDao<TItem> implements Dao<TItem> {
protected final SQLiteDatabase database;
protected final SqlUtilities sqlUtilities;
public AbstractDao(SQLiteDatabase database, SqlUtilities sqlUtilities) {
this.database = database;
this.sqlUtilities = sqlUtilities;
}
}
Now we need Dao's for RelatedTable and for UserTable:
public class UserRelation {
public String mainUserId;
public String relatedUserId;
}
...
public interface UserRelationDao extends Dao<UserRelation> {
...
List<User> getFriends(String userId);
...
}
...
public interface UserDao extends Dao<User> {
...
void addWithIgnore(List<TItem> items);
void update(List<TItem> items);
void upsert(List<TItem> items);
User getById(String userId);
...
}
Once it's done, we can actually implement this interfaces:
DefaultUserRelationDao class:
public class DefaultUserRelationDao extends AbstractDao<UserRelation> implements UserRelationDao {
static final String MAIN_USER_COLUMN = "mainuser";
static final String RELATED_USER_COLUMN = "relateduser";
private static final String[] COLUMN_NAMES = new String[]{
MAIN_USER_COLUMN,
RELATED_USER_COLUMN,
};
private static final String[] COLUMN_TYPES = new String[]{
"TEXT",
"TEXT",
};
private static final String TABLE = "userrelation";
static final String CREATE_TABLE = SqlUtilities.getCreateStatement(TABLE, COLUMN_NAMES, COLUMN_TYPES);
static final String ALL_CONNECTED_USERS =
"SELECT " + Joiner.on(",").join(DefaultUserDao.COLUMN_NAMES) +
" FROM " + UserTable.TABLE_NAME + "," + TABLE +
" WHERE " + RELATED_USER_COLUMN + "=" + DefaultUserDao.USER_ID_COLUMN;
public DefaultUserRelationDao(SQLiteDatabase database, SqlUtilities sqlUtilities) {
super(database, sqlUtilities);
}
#Override
public void add(List<UserRelation> userRelations) {
try {
database.beginTransaction();
ContentValues contentValues = new ContentValues();
for (UserRelation relation : userRelations) {
sqlUtilities.setValuesForUsersRelation(contentValues, relation);
database.insertOrThrow(TABLE, null, contentValues);
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}
#Override
public List<User> getFriends(String userId) {
Cursor cursor = database.rawQuery(ALL_CONNECTED_USERS, new String[]{userId});
return sqlUtilities.getConnectedUsers(cursor);
}
}
and DefaultUserDao class:
public final class DefaultUserDao extends AbstractUDao<User> implements UserDao {
public static final String USER_ID_COLUMN = "userid";
static final String USER_NAME_COLUMN = "username";
public static final String[] COLUMN_NAMES = new String[]{
USER_ID_COLUMN,
USER_NAME_COLUMN,
};
private static final String TABLE = "users";
private static final String SELECT_BY_ID =
SqlUtilities.getSelectWhereStatement(TABLE, COLUMN_NAMES, new String[]{ USER_ID_COLUMN });
static final String CREATE_TABLE = SqlUtilities.getCreateStatement(TABLE, COLUMN_NAMES, COLUMN_TYPES);
public DefaultUserDao(SQLiteDatabase database, SqlUtilities sqlUtilities) {
super(database, sqlUtilities);
}
#Override
public void add(List<User> users) {
try {
database.beginTransaction();
ContentValues contentValues = new ContentValues();
for (User user : users) {
sqlUtilities.setValuesForUser(contentValues, user);
database.insertOrThrow(UserTable.TABLE_NAME, null, contentValues);
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}
#Override
public User getById(String userId) {
return getUserBySingleColumn(SELECT_BY_ID, userId);
}
.....
private User getUserBySingleColumn(String selectStatement, String value) {
Cursor cursor = database.rawQuery(selectStatement, new String[]{value});
List<User> users = sqlUtilities.getUsers(cursor);
return (users.size() != 0) ? users.get(0) : null;
}
}
To create our tables, we need to extend SQLiteOpenHelper and in onCreate() actually create tables:
public final class DatabaseHelper extends SQLiteOpenHelper {
static final String DATABASE_NAME = "mysuper.db";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, SCHEMA_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DefaultUserDao.CREATE_TABLE);
db.execSQL(DefaultUserRelationDao.CREATE_TABLE);
}
...
}
Now, I'd suggest to define LocalStorage interface with all possible actions with cache:
get all users
get user by id
add users
add connection between users
etc.
public interface LocalStorage {
User getUserById(String userId);
void addUsers(List<User> users);
....
}
and it's implementation:
public final class SqlLocalStorage implements LocalStorage {
private UserDao userDao;
private UserRelationDao userRelationDao;
private SQLiteDatabase database;
private final Object initializeLock = new Object();
private volatile boolean isInitialized = false;
private SqlUtilities sqlUtilities;
// there database is
// SQLiteOpenHelper helper = new DatabaseHelper(context);
// database = helper.getWritableDatabase();
public SqlLocalStorage(SQLiteDatabase database, SqlUtilities sqlUtilities) {
this.database = database;
this.sqlUtilities = sqlUtilities;
}
#Override
public User getUserById(String userId) {
initialize();
User user = userDao.getById(userId);
if (user == null) {
return null;
}
List<User> relatedUsers = userRelationDao.getFriends(userId);
user.userFriends = relaterUsers;
return user;
}
#Override
public void addUsers(List<User> users) {
initialize();
for (User user : users) {
for (UserBase friend : user) {
UserRelation userRelation = new UserRelation();
userRelation.mainUserId = user.id;
userRelation.relatedUserId = friend.id;
UserRelation userRelationMutual = new UserRelation();
userRelationMutual.mainUserId = friend.id;
userRelationMutual.relatedUserId = user.id;
userRelationDao.add(userRelation);
userRelationMutual.add(userRelation)
}
}
userDao.addWithIgnore(users);
}
void initialize() {
if (isInitialized) {
return;
}
synchronized (initializeLock) {
if (isInitialized) {
return;
}
Log.d(LOG_TAG, "Opens database");
userDao = new DefaultUserDao(database, sqlUtilities);
userRelationDao = new DefaultUserRelationDao(database, sqlUtilities);
isInitialized = true;
}
}
}
Last step - the actual usage of it:
//somewhere in non-UI thread
List<User> users = dataSource.getUsers();
localStorage.addUsers(users);
final User userBob = localStorage.getUserById("42");
NB! I'm heavily using here my custom class SqlUtilities. Unfortunately, it's way too big to post it here, but just an example to give some ideas what's inside - here's how getUsers(Cursor cursor) looks there:
.....
public List<User> getUsers(Cursor cursor) {
ArrayList<User> users = new ArrayList<>();
try {
while (cursor.moveToNext()) {
users.add(getUser(cursor));
}
} finally {
cursor.close();
}
return users;
}
private User getUser(Cursor cursor) {
User user = new User(cursor.getString(0));
user.FullName = cursor.getString(1);
....
return user;
}
.....
I hope, you'll forgive me skipping some details (especially, regarding case, when DB has to be updated, when data is not full and besides getting it from cache, you have to retrieve it from server first, and then load it into the cache, etc). If any crucial part is missing - please, post it in comments and i'll be glad to update the post.
I hope, it will help you.
You can have a look into JSON-RPC. This is a good framework which supports JSON parsing and object mapping of complex object relationship.
I'd say you're trying to solve the wrong problem & the real problem is that your data representation is broken. As well as the circular refs problem its also inefficient in that each friend gets duplicated for each friendship. Better to flatten your list of people like this:
[
{
"id": "13",
"name": "Alice",
"friends": ["24"]
},
{
"id": "24",
"name": "Bob",
"friends": ["13"]
}
]
Store the list in a HashMap<Integer, Person> (or SparseArray<Person>). Job done!
Related
I am creating an Android application which would store lists of values, eg. temperature values for different times under a particular user-defined name. I intend to use SQLite for storing the values, and I read that using Room would provide an ORM layer to it, so I used it. But then I ran into an exception which basically said that I cannot open a database connection from the main thread, so I tried using LiveData for insertion and retrieval purposes. Now I have 2 tables. I'm just trying to show the structure of them without being syntactically accurate:
**PLACE_DETAILS**
place_id integer auto_increment
place_name string
latitude double
longitude double
**TEMPERATURE_DETAILS**
temperature_id integer auto_increment
place_id foreign key references place_details(place_id)
time_of_record timestamp
temperature float
Initially, I thought of not enforcing the foreign key relationship and just retrieving the generated key when I insert the PLACE_DETAILS object, like what Hibernate does, and using it in further insertions into the TEMPERATURE_DETAILS table. However, from this question:
Room API - How to retrieve recently inserted generated id of the entity?
I found that the DAO method itself would need to return a long value representing the generated ID.
#Insert
long insertPlaceDetails(PlaceDetails placeDetails);
However, the AsyncTask which runs in the ViewModel needs to override the doInBackground() method which has Void as the return.
public class PlaceDetailsViewModel extends AndroidViewModel {
private final LiveData<List<PlaceDetails>> placeDetailsList;
private PlaceDatabase placeDatabase;
public PlaceDetailsViewModel(#NonNull Application application) {
super(application);
placeDatabase = PlaceDatabase.getDatabase(this.getApplication());
placeDetailsList = placeDatabase.daoAccess().fetchAllPlaceDetails();
}
public LiveData<List<PlaceDetails>> getPlaceDetailsList() {
return placeDetailsList;
}
public void addPlace(final PlaceDetails placeDetails) {
Log.d("Adding", "Place: " + placeDetails.getPlaceName());
new addAsyncTask(placeDatabase).execute(placeDetails);
}
private static class addAsyncTask extends AsyncTask<PlaceDetails, Void, Void> {
private PlaceDatabase placeDatabase;
addAsyncTask(PlaceDatabase placeDatabase) {
this.placeDatabase = placeDatabase;
}
#Override
protected Void doInBackground(PlaceDetails... placeDetails) {
placeDatabase.daoAccess().insertPlaceDetails(placeDetails[0]);
return null;
}
}
}
So how could I actually retrieve the generated ID in my code? Also, if LiveData won't be able to provide me this value and relationship is the way to go, then also how do I insert values in the TEMPERATURE_DETAILS table based on the foreign key which has been auto-generated in the PLACE_DETAILS table? All the tutorials in the web give examples where they have given the id manually.
EDIT
According to the suggestions given by anhtuannd, I modified my VieModel class. But the value which is being returned is always -1. That itself shows that nothing is being inserted into the database. I have the WRITE_EXTERNAL_STORAGE permission in my manifest. Is anything else required for this to work?
public class PlaceDetailsViewModel extends AndroidViewModel {
private final LiveData<List<PlaceDetails>> placeDetailsList;
private PlaceDatabase placeDatabase;
private long insertId = -1;
public long getInsertId() {
return insertId;
}
public PlaceDetailsViewModel(#NonNull Application application) {
super(application);
placeDatabase = PlaceDatabase.getDatabase(this.getApplication());
placeDetailsList = placeDatabase.daoAccess().fetchAllPlaceDetails();
}
public LiveData<List<PlaceDetails>> getPlaceDetailsList() {
return placeDetailsList;
}
public void onPlaceInserted(long id) {
insertId = id;
}
public void addPlace(final PlaceDetails placeDetails) {
Log.d("Adding", "Place: " + placeDetails.getPlaceName());
new addAsyncTask(placeDatabase).execute(placeDetails);
}
private class addAsyncTask extends AsyncTask<PlaceDetails, Void, Void> {
private PlaceDatabase placeDatabase;
addAsyncTask(PlaceDatabase placeDatabase) {
this.placeDatabase = placeDatabase;
}
#Override
protected Void doInBackground(PlaceDetails... placeDetails) {
insertId = placeDatabase.daoAccess().insertPlaceDetails(placeDetails[0]);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
onPlaceInserted(insertId);
}
}
}
I think you can get ID by using callback function in onPostExecute:
private static class addAsyncTask extends AsyncTask<PlaceDetails, Void, Void> {
private PlaceDatabase placeDatabase;
private PlaceDetails place;
private int insertId = -1;
addAsyncTask(PlaceDatabase placeDatabase) {
this.placeDatabase = placeDatabase;
}
#Override
protected Void doInBackground(PlaceDetails... placeDetails) {
place = placeDetails[0];
insertId = placeDatabase.daoAccess().insertPlaceDetails(place);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
onPlaceInserted(insertId, place);
}
}
void onPlaceInserted(int id, PlaceDetails place) {
// you get ID here
}
Hope all are fine. I am building an application whose database size is increasing. Increase in terms of Data and Tables and associated columns. I have made a dynamic structure to send Models to DatabaseHelper but in the DatabaseHelper i have to put code like instanceOf to make appropriate ContentValues. This is making me annoyed. Dynamic is now going into sort of if else if which of-course i do not want. Please see the following code-snippet to get the better idea.
* Generic Function to insert Persons, it could be Teacher or Student *
public void insertPersons(ArrayList<Person> persons) {
for(Person person : persons) {
insertOrUpdatePerson(person);
}
}
* This is making me annoyed to put instanceof. where is Dynamics now??? *
public void insertOrUpdatePerson(Person person) {
if(person instanceof Teacher)
insertOrUpdateTeacher(person);
else {
ClassStudent student = (ClassStudent) person;
insertOrUpdateStudent(student);
}
}
public void insertOrUpdateStudent(ClassStudent student) {
try {
if(student.getPk_id() > 0) {
updateRecord(getWritableDatabase(), TABLE_STUDENT_DATA, createContentValues(student), student);
} else {
int row_id = insertRecord(getWritableDatabase(), TABLE_STUDENT_DATA, createContentValues(student), student);
student.setPk_id(row_id);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void insertOrUpdateTeacher(Person person) {
try {
Teacher teacher = (Teacher) person;
Cursor cursor = getWritableDatabase().rawQuery("SELECT * FROM " + TABLE_TEACHERS + " WHERE " +
teacher_id + " = " + teacher.getPerson_id(), null);
if(cursor != null && cursor.moveToFirst()) {
teacher.setPk_id(cursor.getInt(cursor.getColumnIndexOrThrow(pk_id)));
}
if(teacher.getPk_id() > 0) {
updateRecord(getWritableDatabase(), TABLE_TEACHERS, createContentValues(teacher), teacher);
} else {
int row_id = insertRecord(getWritableDatabase(), TABLE_TEACHERS, createContentValues(teacher), teacher);
teacher.setPk_id(row_id);
}
} catch (Exception e) {
e.printStackTrace();
}
}
* Multiple tables and multiple content values, code is making me feel bad *
private ContentValues createContentValues(ClassStudent student) {
ContentValues cv = new ContentValues();
cv.put(school_id, student.getSchool_id());
cv.put(student_id, student.getPerson_id());
return cv;
}
private ContentValues createContentValues(Teacher person) {
ContentValues cv = new ContentValues();
cv.put(teacher_id, person.getPerson_id());
cv.put(name, person.getPerson_name());
return cv;
}
What could be the other way to a situation like this? Should Model be
responsible for making contentValues? OR what other architecture
should i follow? I don't want to use ORMs
I suggest you must use Interfaces to tackle this situation.
Create an interface with insert() and getContentValues() and implement them on Base classes.
Example:
public interface PersonInterface {
void insert(ContentValues values);
void getContentValues();
}
public class Person implements PersonInterface {
void insert(ContentValues values) {}
void getContentValues() {}
}
public class Teacher extends Person {
void insert(ContentValues values) {
// your Teacher record insert code goes here
}
void getContentValues() {
// return Teacher content values
}
}
public class Student extends Person {
void insert(ContentValues values) {
// your Student record insert code goes here
}
void getContentValues() {
// return Student content values
}
}
public class DBHelper {
public void insertPersons(ArrayList<Person> persons) {
for(Person person : persons) {
insertOrUpdatePerson(person);
}
public void insertOrUpdatePerson(Person person) {
person.insert(person.getContentValues());
}
}
For SQLite DB structure in android, i generally use the BoD Content provider generator . It is super easy to use wherein you just pass the json values of your table columns and it will automatically generate a content provider class for you. For each new table you can have your separate content provider and this makes the code more efficient.
Moreover, you can just use the command lines commands to generate the structure without importing anything in your gradle file. Please go through the link about which contains all the syntactical information that you would require. Thanks.
I have a database like this:
#Database(name = QuestionDatabase.NAME, version = QuestionDatabase.VERSION)
public class QuestionDatabase {
public static final String NAME = "QuestionDatabase"; // we will add the .db extension
public static final int VERSION = 1;
}
and a table like this:
#Table(database = QuestionDatabase.class)
public class Question extends BaseModel {
#PrimaryKey
public int localID;
#Column
public int Id;
#Column
public String Answer;
#Column
public String ImageURL;
#Column
public boolean IsFavorite;
#Column
public boolean IsSolved;
}
and an asynctask to retrive data from server:
public class QuestionRetriever extends AsyncTask<Integer, Void, Integer> {
private Activity callerActivity;
private QuestionAdapter questionsAdapter;
private List<Question> callerQuestions;
private Integer pageSize = 10;
public QuestionRetriever(Activity callerActivity, QuestionAdapter questionsAdapter, List<Question> questions){
this.callerActivity = callerActivity;
this.questionsAdapter = questionsAdapter;
this.callerQuestions = questions;
}
#Override
protected Integer doInBackground(Integer... pageNumbers) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.33:313/")
.addConverterFactory(GsonConverterFactory.create())
.build();
QuestionWebService service = retrofit.create(QuestionWebService.class);
Call<List<Question>> call = service.getQuestionsPaged(pageNumbers[0].toString(), pageSize.toString());
try {
Response<List<Question>> excecuted = call.execute();
List<Question> questions = excecuted.body();
FastStoreModelTransaction
.insertBuilder(FlowManager.getModelAdapter(Question.class))
.addAll(questions)
.build();
callerQuestions.addAll(questions);
callerActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
questionsAdapter.notifyDataSetChanged();
}
});
//Get TotalQuestionCount if not yet
if (((StatefulApplication) callerActivity.getApplication()).getQuestionCount() == -1){
Call<Integer> call2 = service.getQuestionsSize();
try {
((StatefulApplication) callerActivity.getApplication()).setQuestionCount(call2.execute().body());
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
catch (Exception e){
e.printStackTrace();
}
return 1;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
//TODO: show loader
}
#Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
//TODO: hide loader
}
}
as you see every thing seems ok and eve after running FastStoreModelTransaction nothing wrong happens. no errors.
the init job is done in splash screen activity like this:
private void initializeEveryRun() {
//Initializing DBFlow
//DBFlow needs an instance of Context in order to use it for a few features such as reading from assets, content observing, and generating ContentProvider.
//Initialize in your Application subclass. You can also initialize it from other Context but we always grab the Application Context (this is done only once).
FlowManager.init(new FlowConfig.Builder(getApplicationContext()).build());
}
any idea about what should cause this problem or any solution to try?
TG.
I found the answer!!!
As you see in the model, the Id is the identifier of the object retrieved from server and LocalId is the auto-increment identifier that is stored locally. This was the problem. I've used the Id field as Primary Key and added a field named OnlineId for server side identifer and everything is ok now.
Is this a bug or I was using that wrong?
TG.
This is not execute transaction, it's just transaction creation.
As you can see it this test DBFlow - FastModelTest.kt.
FastStoreModelTransaction
.insertBuilder(FlowManager.getModelAdapter(Question.class))
.addAll(questions)
.build();
You must execute your transaction like this :
FlowManager.getDatabase(QuestionDatabase.class).executeTransaction(<<YourTransaction>>);
Otherwise, if you already had a DatabaseWrapper instance you can do <<YourTransaction>>.excute(<<YourDatabaseWrapper>>);.
Currently I am creating the greenDAO DB connection in a class (which opens the connection in every static method) and using it wherever I need it. But I am not sure if it's the best way of doing it.
Can anyone suggest a better way of doing it?
My Code:
import com.knowlarity.sr.db.dao.DaoMaster;
import com.knowlarity.sr.db.dao.DaoMaster.DevOpenHelper;
import com.knowlarity.sr.db.dao.DaoSession;
import com.knowlarity.sr.db.dao.IEntity;
public class DbUtils {
private static Object lockCallRecord =new Object();
private DbUtils(){};
public static boolean saveEntity(Context context , IEntity entity){
boolean t=false;
DevOpenHelper helper=null;
SQLiteDatabase db=null;
DaoMaster daoMaster=null;
DaoSession daoSession =null;
try{
helper = new DaoMaster.DevOpenHelper(context, IConstant.DB_STRING, null);
db = helper.getReadableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
//Some business logic here for fetching and inserting the data.
}catch (Exception e){
Log.e("saveEntity", e.getStackTrace().toString());
}finally{
if(daoSession!=null)daoSession.clear();
daoMaster=null;
if(db.isOpen())db.close();
helper.close();
}
return t;
}
Your approach causes the database to be loaded very often which is not necessary and may slow down your app significantly.
Open the database once and store it somewhere and request it from there if needed.
Personally I use a global DaoSession and local DaoSessions. The local DaoSessions get used where nothing should remain in the session cache (i.e. persisting a new object into the database, that is likely to be used only very infrequent or performing some queries which will load a lot of entities that are unlikely to be reused again).
Keep in mind that updating entities in a local DaoSession is a bad idea if you use the entity in your global session as well. If you do this the cached entity in your global session won't be updated and you will get wrong results unless you clear the cache of the global session!
Thus the safest way is to either just use one DaoSession or new DaoSessions all the time and to not use a global and local sessions!!!
A custom application class is a good place, but any other class will also be ok.
This is how I do it:
class DBHelper:
private SQLiteDatabase _db = null;
private DaoSession _session = null;
private DaoMaster getMaster() {
if (_db == null) {
_db = getDatabase(DB_NAME, false);
}
return new DaoMaster(_db);
}
public DaoSession getSession(boolean newSession) {
if (newSession) {
return getMaster().newSession();
}
if (_session == null) {
_session = getMaster().newSession();
}
return _session;
}
private synchronized SQLiteDatabase getDatabase(String name, boolean readOnly) {
String s = "getDB(" + name + ",readonly=" + (readOnly ? "true" : "false") + ")";
try {
readOnly = false;
Log.i(TAG, s);
SQLiteOpenHelper helper = new MyOpenHelper(context, name, null);
if (readOnly) {
return helper.getReadableDatabase();
} else {
return helper.getWritableDatabase();
}
} catch (Exception ex) {
Log.e(TAG, s, ex);
return null;
} catch (Error err) {
Log.e(TAG, s, err);
return null;
}
}
private class MyOpenHelper extends DaoMaster.OpenHelper {
public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "Create DB-Schema (version "+Integer.toString(DaoMaster.SCHEMA_VERSION)+")");
super.onCreate(db);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "Update DB-Schema to version: "+Integer.toString(oldVersion)+"->"+Integer.toString(newVersion));
switch (oldVersion) {
case 1:
db.execSQL(SQL_UPGRADE_1To2);
case 2:
db.execSQL(SQL_UPGRADE_2To3);
break;
default:
break;
}
}
}
In application class:
private static MyApplication _INSTANCE = null;
public static MyApplication getInstance() {
return _INSTANCE;
}
#Override
public void onCreate() {
_INSTANCE = this;
// ...
}
private DBHelper _dbHelper = new DBHelper();
public static DaoSession getNewSession() {
return getInstance()._dbHelper.getSession(true);
}
public static DaoSession getSession() {
return getInstance()._dbHelper.getSession(false);
}
Of course you can also store the DaoMaster instead of the DB itself. This will reduce some small overhead.
I'm using a Singleton-like Application class and static methods to avoid casting the application (((MyApplication)getApplication())) every time I use some of the common methods (like accessing the DB).
I would recommend to create your database in your Application class. Then you can create a method to return the DaoSession to get access to the database in other Activities.
I'm developing an Android application in conjunction with Strongloop/Loopback. I've stored my data in a MySQL database and have no problem mapping this with Strongloop/Loopback. However, when retrieving the values from the database using Strongloop/Loopback, the list always return a size but with null values. Can't figure out what is wrong. Can anybody help me with this? Many thanks :)
Here is my json response for the database when access from Strongloop:
[
{
"rewards_image_name": "http://x.x.x.x/projects/images/rewards_1.png",
"rewards_equivalent_points": "10",
"id": 1 }, {
"rewards_image_name": "http://x.x.x.x/projects/images/rewards_2.png",
"rewards_equivalent_points": "20",
"id": 2 }, {
"rewards_image_name": "http://x.x.x.x/projects/images/rewards_3.png",
"rewards_equivalent_points": "30",
"id": 3 }, {
"rewards_image_name": "http://x.x.x.x/projects/images/rewards_4.png",
"rewards_equivalent_points": "40",
"id": 4 }
]
Here is my code for getting the list:
StrongloopClient strongloopClient = new StrongloopClient(this.context);
RestAdapter adapter = strongloopClient.getLoopBackAdapter("Rewards", "GET");
StrongloopRewardsModelRepository strongloopRewardsModelRepository = adapter.createRepository(StrongloopRewardsModelRepository.class);
strongloopRewardsModelRepository.findAll(new ListCallback<StrongloopRewardsModel>() {
#Override
public void onSuccess(List<StrongloopRewardsModel> arg0) {
Log.e("", "GetAllRewards success: " + arg0.get(0).getEquivalentPoints());
}
#Override
public void onError(Throwable arg0) {
Log.e("", "GetAllRewards error: " + arg0);
}
});
Here is the StrongloopClient:
public class StrongloopClient {
private Context context;
private RestAdapter adapter;
public StrongloopClient(Context contxt) {
context = contxt;
}
public RestAdapter getLoopBackAdapter(String transaction, String operation) {
if (adapter == null) {
adapter = new RestAdapter(context, "http://192.168.44.101:3000/api");
adapter.getContract().addItem(
new RestContractItem("/" + transaction, operation),
transaction);
}
return adapter;
}
Here is the code for Repository:
public class StrongloopRewardsModelRepository extends ModelRepository<StrongloopRewardsModel>{
public StrongloopRewardsModelRepository() {
super("Rewards", "Rewards", StrongloopRewardsModel.class);
}
}
And this is the Model:
public class StrongloopRewardsModel extends Model {
private Integer rewardsImageId;
private String rewardImageName;
private String equivalentPoints;
public Integer getRewardsImageId() {
return rewardsImageId;
}
public void setRewardsImageId(Integer rewardsImageId) {
this.rewardsImageId = rewardsImageId;
}
public String getRewardImageName() {
return rewardImageName;
}
public void setRewardImageName(String rewardImageName) {
this.rewardImageName = rewardImageName;
}
public String getEquivalentPoints() {
return equivalentPoints;
}
public void setEquivalentPoints(String equivalentPoints) {
this.equivalentPoints = equivalentPoints;
}
}
Finally, I've found what was wrong. POJOs should match the fields of models created in models.json. Thanks for your time.
However, my other question is that, when I used filters for querying such as passing the filters as part of "parameters" being passed to Strongloop adapter, it seems to return all of the model instances instead of filtered ones. Same code from my question, just that getLoopbackAdapter("Rewards","GET") becomes "Rewards?filter[where][rewards_equivalent_points]=10","GET"). Any ideas why it behaved like that? Thanks :)
This is because your search didn't find any instance. I'm not sure if you need to handle this on your App or if the SDK deals with that.
For example, when trying to search by an element[1] through the rest interface, it gets converted to 404 (not found): rest: {after: convertNullToNotFoundError}
https://github.com/strongloop/loopback-datasource-juggler/blob/master/lib/dao.js#L771