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
}
Related
I have a SQLite Room Database with 5 columns(Day, Likes, Dislikes, plays, Blocked). I have populated the database manually with 8 rows of data in the Database.
From the database I want to retrieve a specific data for a specific day. So I need number of likes (or Dislikes, plays,...) for day 6 (or 1,2,3,4...)
I tried to create the necessary Dao, Repository, and ViewModel methods, but they seems wrong because when I call it in MainActivity it gives me 0 as the return value.
Database Sample:
#Database(entities = Recovery.class, version = 1, exportSchema = false)
public abstract class RecoveryDatabase extends RoomDatabase {
private static RecoveryDatabase INSTANCE;
public abstract RecoveryDao recoveryDao();
// Singleton
public static RecoveryDatabase getInstance(final Context context) {
if (INSTANCE == null) {
synchronized (RecoveryDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), RecoveryDatabase.class, "recovery_database").fallbackToDestructiveMigration().allowMainThreadQueries().addCallback(roomCallback).build();
}
}
}
return INSTANCE;
}
private static RecoveryDatabase.Callback roomCallback = new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
new PopulateDbAsyncTask(INSTANCE).execute();
}
};
private static class PopulateDbAsyncTask extends AsyncTask<Void, Void, Void> {
private RecoveryDao recoveryDao;
private PopulateDbAsyncTask(RecoveryDatabase db) {
recoveryDao = db.recoveryDao();
}
#Override
protected Void doInBackground(Void... voids) {
recoveryDao.insert(new Recovery(1,22,9,2,3));
recoveryDao.insert(new Recovery(2,23,8,3,4));
recoveryDao.insert(new Recovery(3,56,6,4,5));
recoveryDao.insert(new Recovery(4,34,4,2,2));
recoveryDao.insert(new Recovery(5,76,2,7,9));
recoveryDao.insert(new Recovery(6,10,1,4,7));
recoveryDao.insert(new Recovery(7,27,6,7,3));
recoveryDao.insert(new Recovery(8,46,7,1,5));
return null;
}
}
}
Try next options:
Set column name likes explicitly in your DAO (it's not clear for Room what column you want use for value to return):
#Query("SELECT likes FROM recovery_table WHERE day=:day")
int getRecoveryWithDay(int day);
Change type of value your DAO return to Recovery (it is better choice if you want to get several values - likes, dislikes, plays with one query):
#Query("SELECT * FROM recovery_table WHERE day=:day")
Recovery getRecoveryWithDay(int day);
You then can get likes in your Activity:
int day = RecoveryDatabase.getInstance(this).recoveryDao().getRecoveryWithDay(6).likes;
If neither of options work check if there is really row in your database with day = 6 with some sqlite browser
Using the below statement from my PhoneNumbersDao:
// Retrieve entry/entries by contact ID
#Query("SELECT * FROM phone_numbers_table WHERE contact_id = :contactId")
List<PhoneNumber> getPhoneNumbersById(long contactId);
How can I create a method in my AsyncTask to enter my database asynchronously and retrieve all PhoneNumber objects that match the contactId which I pass into it? The code below is what I have so far, but I'm sure where I'm meant to pass contactId:
private static class SelectPhoneNumberByIdAsyncTask extends AsyncTask<PhoneNumber, Void, Void> {
private PhoneNumberDao phoneNumberDao;
private SelectPhoneNumberByIdAsyncTask(PhoneNumberDao phoneNumberDao) {
this.phoneNumberDao = phoneNumberDao;
}
#Override
protected Void doInBackground(PhoneNumber... phoneNumbers) {
phoneNumberDao.insert(phoneNumbers[0]);
return null;
}
}
I am now calling observe() as shown:
WorksideDatabase database = WorksideDatabase.getInstance(getApplication());
PhoneNumberDao phoneNumberDao = database.phoneNumberDao();
phoneNumberDao.getPhoneNumbersById(contactID).observe(this, PhoneNumber-> {
});
private static class SelectPhoneNumberByIdAsyncTask extends AsyncTask<PhoneNumber, Void, Void> {
private PhoneNumberDao phoneNumberDao;
private SelectPhoneNumberByIdAsyncTask(PhoneNumberDao phoneNumberDao) {
this.phoneNumberDao = phoneNumberDao;
}
#Override
protected Void doInBackground(PhoneNumber... phoneNumbers) {
phoneNumberDao.insert(phoneNumbers[0]);
return null;
}
}
This Async should do one thing (insert new phone number) so change class name to InsertPhoneNumAsyncTask
And for retrieve the phone numbers you can use LiveData
#Query("SELECT * FROM phone_numbers_table WHERE contact_id = :contactId")
LiveData<List<PhoneNumber>> getPhoneNumbersById(long contactId);
then you can observe it into your Activity or Fragment like this
getPhoneNumbersById(contactId).observe(this, new Observer<List<PhoneNumber>>() {
#Override
public void onChanged(#Nullable List<PhoneNumber> numbers) {
//here you access the returned numbers
}
});
Check this hope it helps you
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>>);.
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 have an application where I want to search for a book using ISBN and display it on the screen. The Book objects are stored in a Realm database with ISBN as primary key. If they are not stored in the database, they are retrieved asynchronously (ASyncTask) from a server and then stored in the database.
Now, I am unfamiliar with Realm and how to use it.
In the code below, would ViewBookActivity and MainController run on the same thread, and therefore use the same database instance? Would returning a Realm object from a "static class" be a problem?
How can I guarantee that calling MainController.getBook() always returns a book? The way it works now, is that when getBook is called and the book is not in the database, it returns null.
Is a changelisteners in each Activity that uses the MainController the only way? I want to avoid, if possible, to use/reference Realm at all in the activities and make them get objects through the MainController instead.
public class RealmActivity extends Activity {
private Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.deleteRealm(realmConfiguration); // Clean slate
Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
public class ViewBookActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
ISBN = intent.getStringExtra("ISBN");
setContentView(R.layout.activity_scan_result);
setBook(ISBN);
}
public void setBook(String ISBN) {
Book b = MainController.getBook(ISBN);
// Display book on screen
}
}
public class MainController {
static Realm realm = Realm.getDefaultInstance();
public static Book getBook(String isbn) {
Book book = realm.where(Book.class)
.equalTo("isbn", isbn)
.findFirst();
if (book == null) {
NetworkController.getBook(isbn);
} else {
return book;
}
return null;
}
}
public class NetworkController {
private static BookHandler bookHandler = new BookHandler();
public static void getBook(String isbn) {
NetworkHelper.sendRequest(HTTPRequestMethod.GET,
"/books/" + isbn, bookHandler);
}
}
public class NetworkHelper {
private static String host = "http://crowdshelf-dev.herokuapp.com";
public static void sendRequest(final HTTPRequestMethod requestMethod, final String route, final ResponseHandler responseHandler) {
new AsyncTask<Void, Void, Void>() {
#Override
protected void doInBackground(Void... params) {
try {
URL url = new URL(host + "/api" + route);
Log.d("NETDBTEST", "NetworkHelper request: " + requestMethod.toString() + " URL: " + url.toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
BufferedReader bReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder builder = new StringBuilder();
String line = null;
while ((line = bReader.readLine()) != null) {
builder.append(line).append("\n");
}
String jsonString = builder.toString();
responseHandler.handleJsonResponse(jsonString);
return null;
}
}
}.execute();
}
}
public class BookHandler implements ResponseHandler {
#Override
public void handleJsonResponse(String jsonString) {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.createOrUpdateObjectFromJson(Book.class, jsonString);
realm.commitTransaction();
realm.close();
}
}
public class Book extends RealmObject{
#PrimaryKey
private String isbn;
// Other fields, getters and setters
}
1) First, I don't know why you created RealmActivity just for cleaning database. Activities are for showing UI. If it doesn't have any purpose then move the code to some other class. Like you can create a new class RealmHandler and keep this kind of code there.
2) You don't really need to create a static function to get Realm object. You could have your MainController function like this:
public class MainController {
public Book getBook(String isbn, Context context) {
Realm realm = Realm.getDefaultInstance(context);
Book book = realm.where(Book.class)
.equalTo("isbn", isbn)
.findFirst();
if (book == null) {
NetworkController.getBook(isbn);
} else {
return book;
}
return null;
}
}
3)
How can I guarantee that calling MainController.getBook() always
returns a book?
You can't guarantee that you always get a Book, for example a Book isn't found on server, or there's no internet connection. You have to handle that case manually.
sendBook function calls AsyncTask and AsyncTask runs on a separate thread so you can't return your server results when you call NetworkController.getBook(isbn). The only way to get results is in BookHandler. handleJsonResponse
By the way, you don't need to implement Handler to get results from AsyncTask. You can override onPostExecution function to get control of your results and perform UI on Main thread.
Solution:
You can handle this in different ways as you like, here's one of the way:
i) Call MainController.getBook() to get a book. If Book isn't found then run NetworkController.getBook(isbn); - This will run on a different Thread and current Thread will send back null to your Activity. If null then you just ignore it or perform action accordingly.
ii) In AsyncTask you could first show a ProgressBar to tell user something is in Process while you are getting data from Server. You could do this in onPreExecution function of AsyncTask.
iii) Get results in onPostExecution function of AsyncTask. Now you are back on your main thread. You can directly update your UI if you are in the same Activity, but in your case you are not. You can't directly return data like in a function or update UI.
iv) You can send a signal to your MainActivity that network call has finished and new data is in the database so update view. You can could use Event bus for this purpose. I personally use Otto by Square: http://square.github.io/otto/