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.
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
}
So I have 2 EditText fields with some text which I want to save and load in a later date. What is the best approach in doing it? It must be saved on the phone and not on an online internet database(Like Firebase).
Solutions I have thought of are:
1. Save it in a single text file with the .txt extension removed.
a. Here I will use keywords which my code can read like <START OF DATA(x)>
where x is the id of the data saved.(
b. This obviously will take a long time to implement but it will make. But
will be a lot neater than solution #2.
2. Save each result to separate text file with the .txt extension removed.
a. Here I will save results to their corresponding text file(Ex: res0001).
b. I will use a third party file explorer to load the text contained in the
text file.
c. This is easier to implement than solution #1.
Any other suggestions on how to best approach my problem? An API perhaps?
You can save the value in Preferences. Below class will be make easy for you to save data and retrive it from Preferences
public class SessionManager {
private SharedPreferences pref;
private static SessionManager sessionManager;
public static SessionManager getInstance(Context context) {
if(sessionManager == null){
sessionManager = new SessionManager(context);
}
return sessionManager;
}
public SessionManager(Context context) {
String PREF_NAME = context.getResources().getString(R.string.app_name);
this.pref = context.getSharedPreferences(PREF_NAME,Context.MODE_PRIVATE);
}
/**
* Getting value for key from shared Preferences
*
* #param key key for which we need to get Value
* #param defaultValue default value to be returned if key is not exits
* #return It will return value of key if exist and defaultValue otherwise
*/
public String getValueFromKey(String key, String defaultValue) {
if (pref.containsKey(key)) {
return pref.getString(key, defaultValue);
} else {
return defaultValue;
}
}
/**
* Setting value for key from shared Preferences
*
* #param key key for which we need to get Value
* #param value value for the key
*/
public void setValueFromKey(String key, String value) {
pref.putString(key, value).apply();
}
/**
* Setting value for key from shared Preferences
*
* #param key key for which we need to get Value
* #param value value for the key
*/
public void setFlagFromKey(String key, boolean value) {
pref.putBoolean(key, value).apply();
}
/**
* To get Flag from sharedPreferences
*
* #param key key of flag to get
* #return flag value for key if exist. false if not key not exist.
*/
public boolean getFlagFromKey(String key) {
return pref.containsKey(key) && pref.getBoolean(key, false);
}
}
Lex_F you can use the SharedPreferences but if user clear the app data or cache of your application from setting then your SharedPreferences gives the default value rather than your actual saved value.For permanent storage, you can use the SQLite database but SQLite required more boilerplate code.I think you should try to use the realm database which is very simple to use.This up to you which database should use.But I would give the example of real database like.
public class UserInformation extends RealmObject {
private String name;
#PrimaryKey //define the phone number as a primary key
private String phone;
private String address;
private String gmail;
public UserInformation() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getGmail() {
return gmail;
}
public void setGmail(String gmail) {
this.gmail = gmail;
}
}
Now in the any activity
Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Realm.init(this);
realm = Realm.getDefaultInstance();
//insert the data into the realm database
insert.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
//second argument represent the primary key
UserInformation information = realm.createObject(UserInformation.class, phone.getText().toString());
information.setName(name.getText().toString());
information.setGmail(email.getText().toString());
information.setAddress(address.getText().toString());
realm.copyToRealm(information);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Toast.makeText(MainActivity.this, "Successfully inserted data..", Toast.LENGTH_SHORT).show();
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Toast.makeText(MainActivity.this, "Something wrong please try again later..", Toast.LENGTH_SHORT).show();
}
});
}
});
update.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<UserInformation> query = realm.where(UserInformation.class).equalTo("phone", phone.getText().toString()).findAll();
//if query not gives any result then query.size() return give 0 value
if (query.size() == 0) {
ToastLogUtil.toastmessage(MainActivity.this, "You entered wrong information or might be your entered phone no not matches existing information");
} else {
for (UserInformation info : query) {
info.setName(name.getText().toString());
// info.setPhone(phone.getText().toString());
info.setGmail(email.getText().toString());
info.setAddress(address.getText().toString());
realm.copyToRealm(info);
}
ToastLogUtil.toastmessage(MainActivity.this, "Successfully updated data");
}
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
ToastLogUtil.toastmessage(MainActivity.this, "Your Information Updated Successfully..");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
clearData();
ToastLogUtil.toastmessage(MainActivity.this, "Your Information not Updated something wrong...");
ToastLogUtil.errorlog(error.toString());
}
});
}
});
retrieve.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (TextUtils.isEmpty(name.getText().toString()) && TextUtils.isEmpty(phone.getText().toString()) && TextUtils.isEmpty(email.getText().toString()) && TextUtils.isEmpty(address.getText().toString())) {
//at this you can retrieve all information
RealmResults<UserInformation> query = realm.where(UserInformation.class).findAllAsync();
for (UserInformation info : query) {
text.append(info.getName() + "\n");
text.append(info.getPhone() + "\n");
text.append(info.getGmail() + "\n");
text.append(info.getAddress() + "\n");
}
ToastLogUtil.toastmessage(MainActivity.this,"retrieve all data successfully...");
} else {
//at this query you can retrieve specific user which have a same phone which you enter in .equalTO() method
RealmResults<UserInformation> query = realm.where(UserInformation.class).equalTo("phone", phone.getText().toString()).findAll();
for (UserInformation info : query) {
text.append(info.getName() + "\n");
text.append(info.getPhone() + "\n");
text.append(info.getGmail() + "\n");
text.append(info.getAddress() + "\n");
ToastLogUtil.toastmessage(MainActivity.this,"Retrieve "+info.getPhone()+ " data successfully...");
}
}
}
});
delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (TextUtils.isEmpty(phone.getText().toString())) {
ToastLogUtil.toastmessage(MainActivity.this, "Please mention the phone number you want to delete");
} else {
RealmResults<UserInformation> query = realm.where(UserInformation.class).equalTo("phone", phone.getText().toString()).findAllAsync();
if (query.size() == 0) {
ToastLogUtil.toastmessage(MainActivity.this,"Your phone no not matches in our database phone no..");
} else {
realm.beginTransaction();
query.deleteAllFromRealm();
realm.commitTransaction();
ToastLogUtil.toastmessage(MainActivity.this, "Delete data successfully...");
}
}
}
});
}
Thank you :-)
Not forget to add the realm dependencies
//Add the class path dependency to the project level build.gradle file.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:4.3.3"
}
}
//Apply the realm-android plugin to the top of the application level build.gradle file.
apply plugin: 'realm-android'
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!
I want to call CRUD operations on Order objects in my Activity. I was wondered is the following implementation of a "Service" class a good way to do this? I don't want any reference to DatabaseHelper or DAO objects in my Activity code as I don't think this would be desireable.
Here is my Service class
public class OrderService
{
private static OrderService instance;
private static Dao<Order, Integer> orderDAO;
static public void init(Context ctx) {
if (null == instance) {
instance = new OrderService(ctx);
}
}
public static OrderService getInstance() {
return instance;
}
private OrderService(Context ctx) {
DatabaseHelper helper = DatabaseHelper.getInstance(ctx);
helper.getWritableDatabase();
orderDAO = helper.getOrderDao();
}
public Order getOrderWithId(int orderId) {
Order myOrder = null;
try {
myOrder = orderDAO.queryForId(orderId);
} catch (SQLException e) {
e.printStackTrace();
}
return myOrder;
}
public Order neworder(Order order) {
try {
orderDAO.create(order);
} catch (SQLException e) {
e.printStackTrace();
}
return order;
}
public void deleteorder(Order order) {
try {
orderDAO.delete(order);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void updateorder(Order order) {
try {
orderDAO.update(order);
} catch (SQLException e) {
e.printStackTrace();
}
}
public List <Order> getordersForCategory(int orderId) {
List <Order> orders = null;
try {
orders = orderDAO.queryForAll();
} catch (SQLException e) {
e.printStackTrace();
}
return orders;
}
}
and here is how I intend to use the service
public class OrderProcessingActivity extends Activity {
int orderID;
private Order order;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myview);
order = OrderService.getInstance().getOrderWithId(orderID);
......
Does this look like a good way to access the SQLlite DB ?
I have read about "Service" implementations that can be configured in Android so I was sondered is this something that I should be using instead?
Despite moving your database logic to a different class, you're doing all of your database operations in the UI thread, which is not ideal. Also note that even though your class is called "service" it doesn't inherit from any of the Service classes in Android.
One alternate approach would be to do your database operations from the doInBackground method of an AsyncTask, return your needed data from that method. Then, use the returned data to update your activity in the onPostExecute method.
This is more or less the approach I take. My application architecture typically looks like this:
Activity <--> Service <--> DAO <--> SQLite
This looks pretty close to what you have, so I'd say it looks good! I normally don't implement it as a singleton, however, as I don't like to keep the same Context around for the entire lifetime of the app. Instead, I pass in the Context to create a service from each Activity.
I have these to classes:
public class Station {
#DatabaseField(foreign = true, foreignAutoCreate = true)
private OpeningTimes openingTimes;
}
public class OpeningTimes {
#DatabaseField(generatedId = true)
int _id;
}
Now OpeningTimes row is auto created, when I call createOrUpdate method on StationDao. That's great!
I would be also thankful, if I could delete Station object and its nested object OpeningTimes automatically.
Now I have to do it this way in Station class and it seems quite confusing. Is there any more elegant way?
public void deleteFromDb(DatabaseHelper dbHelper) {
try {
openingTimes.deleteFromDb(dbHelper);
dbHelper.getStationDao().delete(this);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
EDIT:
I have been trying also this, but with SQL Statement errors
#DatabaseField(foreign = true, foreignAutoCreate = true, columnDefinition="INTEGER, FOREIGN KEY(`openingTimes_id`) REFERENCES openingtimes(`_id`)")
I would consider doing this at the DAO level instead of at the persisted object level. What I recommend is creating your own StationDao interface and your own StationDaoImpl implementation. The ORMLite docs an example of this.
public interface StationDao extends Dao<Station, Integer> {
// we will just be overriding some of the delete methods
}
Then create your implementation which would override the delete() method and delete any children objects. Something like the following:
public class StationDaoImpl extends BaseDaoImpl<Station, Integer>
implements StationDao {
private final Dao<OpeningTimes, Integer> openTimesDao;
public AccountDaoImpl(ConnectionSource connectionSource) throws SQLException {
super(connectionSource, Station.class);
openTimesDao = DaoManager.createDao(connectionSource, OpeningTimes.class);
}
#Override
public int delete(Station station) throws SQLException {
if (station.openTimes != null) {
openTimesDao.delete(station.openTimes);
}
return super.delete(station);
}
}
If you are using your own DAO then you would have to make sure it is configured using #DatabaseTable(daoClass = StationDaoImpl.class).