Using AsyncTask in Activity fails - java

I have an application in which I wanted to store a list of items and I figured a database would do. I found out about the new Room API and tried using it, though I'm having some trouble getting it working. I have a background service which is supposed to write entries to the database. I read that using the singleton pattern was recomended, but I can't seem to get it working. When I try to retrieve all entries in my MainActivity, the list I get back is always empty, indicating that I wasn't able to save them from the start.
Singleton db class
#Database(entities = {TemperatureReading.class}, version = 1)
public abstract class DatabaseSingleton extends RoomDatabase {
private static DatabaseSingleton INSTANCE;
public abstract TemperatureReadingDao temperatureReadingDao();
public static DatabaseSingleton getAppDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context.getApplicationContext(), DatabaseSingleton.class, "fireTempDatabase")
.build();
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
}
Entity
#Entity
public class TemperatureReading {
#PrimaryKey(autoGenerate = true)
private int uid;
#ColumnInfo(name = "dateTime")
private long dateTime;
#ColumnInfo(name = "location")
private String readingLocation;
#ColumnInfo(name = "value")
private float value;
public long getDateTime() {
return dateTime;
}
public void setDateTime(long dateTime) {
this.dateTime = dateTime;
}
public String getReadingLocation() {
return readingLocation;
}
public void setReadingLocation(String readingLocation) {
this.readingLocation = readingLocation;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
}
EntityDAO
#Dao
public interface TemperatureReadingDao {
#Query("SELECT * FROM temperatureReading")
List<TemperatureReading> getAll();
#Query("SELECT * FROM temperatureReading ORDER BY uid desc limit 1")
TemperatureReading getLatest();
#Insert
void insertAll(TemperatureReading... temperatureReading);
#Update
void update(TemperatureReading... temperatureReading);
#Delete
void delete(TemperatureReading temperatureReading);
}
Background service which saves to db
private void saveTempDatabase(float tmpMessageAsFloat, long tmpMessageDateTime) {
Log.d(TAG, "saveTempDatabase");
TemperatureReading tr = new TemperatureReading();
tr.setDateTime(tmpMessageDateTime);
tr.setReadingLocation("XXX"); //TODO
tr.setValue(tmpMessageAsFloat);
DatabaseSingleton.getAppDatabase(getApplicationContext()).temperatureReadingDao().insertAll(tr);
}
MainActivity were db is read from, uses Async task so it doesn't block UI
private void updateTemperature() {
Log.d(TAG, "updateTemperature");
new AsyncTask<Void, Void, Integer>() {
#Override
protected Integer doInBackground(Void... params) {
List<TemperatureReading> tr = DatabaseSingleton.getAppDatabase(MainActivity.this).temperatureReadingDao().getAll(); //List is always empty, no matter how many times I have called the saveTempDatabase() method in the service class.
return 0;
}
#Override
protected void onPostExecute(Integer agentsCount) {
}
}.execute();
}
Maybe it has to do with the context somehow?
EDIT:
Just tried adding .allowMainThreadQueries() when building the database and now it works. So for some reason my Async task isn't working?

Your AsyncTask seems to be wrong. You should return the list you would like to handle in doInBackground and then expect it in onPostExecute. Why do you always return 0?

Related

Android: Getting the count of items with a specific code in a Room database

I am essentially trying to create some kind of a search feature. I am using a Room database and every element has a specific 3-digit code submitted by the user. However, when I try to get the number of elements with a specific 3-digit code and display it in a Toast message in Main Activity, I always get 0 as a result.
Each entity of my table is an object of a Word class I have created as shown here:
#Entity(tableName = "word_table")
public class Word {
#PrimaryKey(autoGenerate = true)
#NonNull
private int uid;
#ColumnInfo(name = "word_code")
private int wordCode;
#ColumnInfo(name = "word")
private String word;
public Word(int uid, int wordCode, String word){
this.uid = uid;
this.wordCode = wordCode;
this.word = word;
}
public int getUid(){
return uid;
}
public int getWordCode(){
return wordCode;
}
public String getWord(){
return word;
}
}
My DAO class looks like this:
#Dao
public interface WordDao {
#Insert
void insert(Word word);
#Query("SELECT COUNT(*) FROM word_table WHERE word = :selectedCode")
LiveData<Integer> getWordWithCode(int selectedCode);
}
My class that extends the RoomDatabase looks like this:
#Database(entities = {Word.class}, version = 1, exportSchema = false)
public abstract class WordRoomDatabase extends RoomDatabase {
public abstract WordDao wordDao();
private static WordRoomDatabase INSTANCE;
public static WordRoomDatabase getDatabase(final Context context){
if (INSTANCE == null){
synchronized (WordRoomDatabase.class){
if (INSTANCE==null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), WordRoomDatabase.class, "word_database")
.fallbackToDestructiveMigration()
.addCallback(sDatabasePopulateCallback)
.build();
}
}
}
return INSTANCE;
}
private static RoomDatabase.Callback sDatabasePopulateCallback = new RoomDatabase.Callback(){
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
new PopulateDatabaseAsync(INSTANCE).execute();
}
};
private static class PopulateDatabaseAsync extends AsyncTask<Void, Void, Void> {
private WordDao mWordDao;
PopulateDatabaseAsync(WordRoomDatabase db){
mWordDao = db.wordDao();
}
int[] discCodes = {232, 432, 523,233,242,554,221};
String[] discNames = {"First Word", "Second Word", "Third Word", "Fourth Word", "Fifth Word", "Sixth word", "Seventh Word"};
#Override
protected Void doInBackground(Void... voids) {
for (int i = 0;i < discCodes.length; i++){
Word newWord = new Word(0, discCodes[i], discNames[i]);
mWordDao.insert(newWord);
}
return null;
}
}
}
I am using the discCodes and discNames arrays to populate the list as a sample.
This is what my Repository looks like:
public class WordRepository {
private WordDao mWordDao;
private LiveData<Integer> mCount;
int thiscount;
WordRepository(Application application){
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
}
public void insert(Word word){
new insertWordAsync(mWordDao).execute(word);
}
private static class insertWordAsync extends AsyncTask<Word, Void, Void>{
private WordDao mDao;
insertWordAsync(WordDao dao){
mDao = dao;
}
#Override
protected Void doInBackground(Word... words) {
mDao.insert(words[0]);
return null;
}
}
public LiveData<Integer> getCount(int givenCode){
return mWordDao.getWordWithCode(givenCode);
}
}
This is the ViewModel class:
public class WordViewModel extends AndroidViewModel {
private WordRepository mRepo;
public WordViewModel(#NonNull Application application) {
super(application);
mRepo = new WordRepository(application);
}
public void insert(Word word){
mRepo.insert(word);
}
public LiveData<Integer> getCount(int givenCode){
return mRepo.getCount(givenCode);
}
}
And finally the MainActivity class:
public class MainActivity extends AppCompatActivity {
private WordViewModel mWordViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);
mWordViewModel.getCount(242).observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
String toShow = Integer.toString(integer);
Toast.makeText(getApplicationContext(), toShow, Toast.LENGTH_SHORT).show();
}
});
}
}
Now in this particular example I am using the code 242 as the base of my search. I first tried using a return type of int instead of LiveData, meaning my DAO Query would like like this:
#Query("SELECT COUNT(*) FROM word_table WHERE word = :selectedCode")
int getWordWithCode(int selectedCode);
After some search I figured using LiveData and an Observer is preferable.
However when I run the app, instead of getting 1 in the Toast message, I get 0.
I am new to android development so I apologize for the messy code and for pasting all this code. I have been learning about Room with the Google Codelabs tutorials, so feel free to suggest me any learning resources if having a better understanding of Room will solve my problem.
Thanks in advance!
You are actually searching word instead of wordCode
Try this code in WordDao
#Query("SELECT COUNT(*) FROM word_table WHERE word_code = :selectedCode")
LiveData<Integer> getWordWithCode

Room #Insert does not insert all records

I am running into an issue where only 1 record is being inserted into my Room SQLite DB.
When I perform a getAll(); the result only returns 1 record.
FOUND ISSUE: Genre[] genres = gson.fromJson(jsonArray.toString(), Genre[].class);
This line above is setting all "gid" values to 0, and I am not sure how to change that.
Genre.java
#Entity(indices = {#Index(value = {"id", "name"}, unique = true)})
public class Genre {
#PrimaryKey
private int gid;
//#ColumnInfo(name = "id") By Default - No need to annotate
#NonNull
private int id;
private String name;
public int getGid() {
return gid;
}
public void setGid(int gid) {
this.gid = gid;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
GenreDao.java
#Dao
public interface GenreDao {
#Query("SELECT * FROM Genre")
LiveData<List<Genre>> getAll();
#Insert(onConflict = OnConflictStrategy.REPLACE) //If there is a conflict, replace the record.
void insertAll(Genre... genres);
}
GenreRepository.java
public class GenreRepository {
private final GenreDao genreDao;
public GenreRepository(GenreDao genreDao) {
this.genreDao = genreDao;
}
//Database Methods
public void insertAll(Genre... genres) {
AsyncTask.execute(() -> { //Same as new Runnable()
genreDao.insertAll(genres);
});
}
public LiveData<List<Genre>> getAll() {
return genreDao.getAll();
}
}
APIUtil.java - getGenres() Method
This class makes an API call, returns the proper results, converts the JSONArray to a Genre[]. I can successfully loop through the Genre[] and confirm 10+ results come back.
public static void getGenres(Context context) {
APIWrapper wrapper = new APIWrapper(context, API_KEY);
Parameters params = new Parameters();
params.addFields(GENRE_FIELDS);
params.addLimit("50");
wrapper.genres(params, new onSuccessCallback() {
#Override
public void onSuccess(JSONArray jsonArray) {
Gson gson = new Gson();
Genre[] genres = gson.fromJson(jsonArray.toString(), Genre[].class);
//Insert DB
AppDatabase db = AppDatabase.getAppDatabase(context);
GenreRepository genreRepository = new GenreRepository(db.genreDao());
genreRepository.insertAll(genres);
}
#Override
public void onError(VolleyError volleyError) {
Log.e("GENRES ERROR:", volleyError.toString());
}
});
}
GenreViewModel.java
public class GenreViewModel extends ViewModel {
private GenreRepository genreRepository;
public GenreViewModel(GenreRepository genreRepository) {
this.genreRepository = genreRepository;
}
public void insertAll(Genre... genres){
genreRepository.insertAll(genres);
}
public LiveData<List<Genre>> getAll(){
return genreRepository.getAll();
}
}
SearchFragment.java
This is where I am retrieving the database values. This for loop only returns 1 result.
AppDatabase db = AppDatabase.getAppDatabase(getActivity());
GenreRepository genreRepository = new GenreRepository(db.genreDao());
GenreViewModel genreViewModel = new GenreViewModel(genreRepository);
genreViewModel.getAll().observe(this, genres -> { //new Observer<List<Genre>>()
for(Genre g : genres){
Log.e("GENRE", g.getName());
}
});
public void insertAll(Genre... genres){
genreRepository.insertAll(genres);
}
here is your mistake , what you provide as method definition and what you provide at call. see you make some thing wrong.
Solution
void insertAll(List<T> obj);
you can try with convert your array to list and put above in definition
I had this problem too.
And Solved it this way.
The problem was that the id that comes from server was mongoId and String so I should create a int primary key and pass currentTime as value to it so the database can insert all of them not replace them.
But you should consider using System.nanoTime() method instead of System.currentTimeMillis() cause sometimes it generates same value and then room replace them instead of inserting each one of them.

How to access data inside my ArrayList<Parcelable>

This is my scenario. I get data from a web service and display those data in recycler view. After that I'm planing to add those data in to local sqlite database and display those data when user open application without internet connection. I decided to use IntentService for this so I can save those data without blocking main thread.
I'm using retrofit, GSON and RxJAVA combination to take data from REST service.
retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build();
public void getAllEvent(){
EventService eventService = retrofit.create(EventService.class);
Single<List<Event>> eventsAsSingle = eventService.getEvents();
eventDisposableSingleObserver = eventsAsSingle
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<List<Event>>() {
#Override
public void onSuccess(#NonNull List<Event> events) {
//eventDataList is type of ArrayList<Event>
eventDataList.addAll(events);
EventListAdapter listAdapter = new EventListAdapter(MainActivity.this,eventDataList);
recyclerView.setAdapter(listAdapter);
recyclerView.addItemDecoration(new RecyclerViewDividerVertical(5));
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
//TODO: ADD data into SQLight
Intent intent = new Intent(MainActivity.this,EventCacheService.class);
intent.putParcelableArrayListExtra("event_data",eventDataList);
startService(intent);
}
#Override
public void onError(#NonNull Throwable e) {
Log.d(TAG, "on Error : " + e.getMessage());
}
});
}
Here's my Event class.
import android.os.Parcel;
import android.os.Parcelable;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Event implements Parcelable {
#SerializedName("id")
#Expose
private String id;
#SerializedName("type")
#Expose
private String type;
#SerializedName("actor")
#Expose
private Actor actor;
#SerializedName("repo")
#Expose
private Repo repo;
#SerializedName("payload")
#Expose
private Payload payload;
#SerializedName("public")
#Override
public int describeContents() {
return 0;
}
public Event() {
}
protected Event(Parcel in) {
this.id = in.readString();
this.type = in.readString();
this.actor = in.readParcelable(Actor.class.getClassLoader());
this.repo = in.readParcelable(Repo.class.getClassLoader());
this.payload = in.readParcelable(Payload.class.getClassLoader());
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeString(this.type);
dest.writeParcelable(this.actor, flags);
dest.writeParcelable(this.repo, flags);
dest.writeParcelable(this.payload, flags);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Actor getActor() {
return actor;
}
public void setActor(Actor actor) {
this.actor = actor;
}
public Repo getRepo() {
return repo;
}
public void setRepo(Repo repo) {
this.repo = repo;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
public static final Parcelable.Creator<Event> CREATOR = new Parcelable.Creator<Event>() {
#Override
public Event createFromParcel(Parcel source) {
return new Event(source);
}
#Override
public Event[] newArray(int size) {
return new Event[size];
}
};
}
My service class.
public class EventCacheService extends IntentService {
public EventCacheService() {
super("EventCacheService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
ArrayList<Parcelable> eventData = intent.getParcelableArrayListExtra("event_data");
System.out.println("yeee");
}
}
}
If I set a break point on System.out.println() I can see that I have taken all the data from my activity to Service successfully.
But I can't find a way to access those Event type objects. As suggested in most places I can't convert these objects in to Event type by simply doing.
(ArrayList<Event>)intent.getParcelableArrayListExtra("event_data");
I'm getting an error saying Error:(20, 99) error: incompatible types: ArrayList<Parcelable> cannot be converted to ArrayList<Event>
Please point out what I'm going wrong. Is there any better way to handle this ?
You problem is error: incompatible types: ArrayList<Parcelable> cannot be converted to ArrayList<Event>
So change
ArrayList<Parcelable> eventData = intent.getParcelableArrayListExtra("event_data");
to
ArrayList<Event> eventData = intent.getParcelableArrayListExtra("event_data");
Sorry, My bad. I can access those objects like this.
for (Parcelable parceledEvent: eventData){
Event event = (Event) parceledEvent;
String type = event.getType();
Log.d(TAG,"type: "+type);
}

Spring Boot API returns json without labels

I'm building a rest API with Java Spring Boot and I'm running into a problem, I have the following class with a method (which is in my controller for testing purposes, I will send its logic to the service later):
#RestController
#RequestMapping("/api/readings")
public class Readings {
#Autowired
private ReadingService readingService;
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public List<Reading> getRelevant(#RequestParam("start") String start, #RequestParam("end") String end){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:MM:SS");
start += " 00:00:00";
end += " 23:59:59";
try {
Date startdate = df.parse(start);
Date enddate = df.parse(end);
return readingService.getRelevant(startdate, enddate);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}
This makes use of a service that calls the following repository function:
#Query("SELECT pmtwofive, pmten, recording FROM Reading WHERE recording >= ?1 AND recording <= ?2")
List<Reading> getRelevant(Date start, Date end);
Everything works fine, except for the format of the result:
[[10,20,1505801743816],[14,21,1505802311976],[14,21,1505802330610],[10,13,1505803302960],[10,13,1505803321966]]
Instead of this, I was expecting something like I get when using the CrudRepository from hibernate querying my whole table instead of just these three values:
{
{
pmtwofive: 10,
pmten: 20,
reading: 1505801743816
},
{
...
}
}
What should I do to get my expected result? Thank you!
Reading Class:
package com.amione.models;
import javax.persistence.*;
import java.sql.Timestamp;
#Entity
public class Reading {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(columnDefinition = "serial")
private long reading_id;
private int sensor_id;
private int function;
private int directionstart;
private int pmtwofive;
private int pmten;
private int checksumlow;
private int checksumhigh;
private Timestamp recording;
public long getReading_id() {
return reading_id;
}
public void setReading_id(int reading_id) {
this.reading_id = reading_id;
}
public int getSensor_id() {
return sensor_id;
}
public void setSensor_id(int sensor_id) {
this.sensor_id = sensor_id;
}
public int getFunction() {
return function;
}
public void setFunction(int function) {
this.function = function;
}
public int getDirectionstart() {
return directionstart;
}
public void setDirectionstart(int directionstart) {
this.directionstart = directionstart;
}
public int getPmtwofive() {
return pmtwofive;
}
public void setPmtwofive(int pmtwofive) {
this.pmtwofive = pmtwofive;
}
public int getPmten() {
return pmten;
}
public void setPmten(int pmten) {
this.pmten = pmten;
}
public int getChecksumlow() {
return checksumlow;
}
public void setChecksumlow(int checksumlow) {
this.checksumlow = checksumlow;
}
public int getChecksumhigh() {
return checksumhigh;
}
public void setChecksumhigh(int checksumhigh) {
this.checksumhigh = checksumhigh;
}
public Timestamp getRecording() {
return recording;
}
public void setRecording(Timestamp recording) {
this.recording = recording;
}
}
Ok I have the answer. It must be done with custom constructor:
#Data
#EqualsAndHashCode(callSuper = true)
#Entity
public class City extends AbstractEntity {
#Column
private String name;
#Embedded
private Geopoint centre=new Geopoint();
public City(){}
public City(String name){
this.setName(name);
}
// #OneToMany(mappedBy="city")
// private Set<Place> places;
}
Repository:
public interface CityRepository extends JpaRepository<City, Long>{
City findOneByName(String name);
#Query("SELECT name FROM City")
public List<City> findMethod1();
#Query("SELECT c.name FROM City c")
public List<City> findMethod2();
Controller:
#Autowired
private CityRepository cityRepository;
#GetMapping("/test")
public List<City> test(){
List<City> ret=new ArrayList();
ret.addAll(cityRepository.findMethod1());
ret.addAll(cityRepository.findMethod2());
ret.addAll(cityRepository.findMethod3());
return ret;
}
and the result:
As you can see, 3rd method works. I told you it will came up.
As empty values are still serialized, you can use a DTO object to encapsule only required fields (and SELECT new EntityDTO(field1,field2,field3) FROM Entity)
Another option would bo to configure Jackson to not to serialize null values with annotation or configuratio, but that is just beyond the scope of question.

getting zero objects from BackendlessUser getProperty()

I've setup Backendless app, with User table having row name posts, which is data object relationship between User and Post tables, data relation is 1 to many. When I try to retrieve it using BackendlessUser.getProperty("key"), it returns zero objects. Once it even returned HashMap. I used the way mentioned in the the docs, but get null because the size of the Object[] is zero. I have data in that row for sure, wrote <uses-permission> for internet.
public class MainActivity extends AppCompatActivity {
BackendlessUser user = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Backendless.initApp(this, "app_id", "android_key", "v1");
Backendless.UserService.login("username", "password", new AsyncCallback<BackendlessUser>() {
#Override
public void handleResponse(BackendlessUser response) {
user = response;
doStuff();
}
#Override
public void handleFault(BackendlessFault fault) {
Log.e("Error logging in", "message: " + fault.getMessage());
}
});
}
//important
private void doStuff() {
Object[] postObjectArray = (Object[]) user.getProperty("posts");
Post[] posts = null;
if (postObjectArray != null && postObjectArray.length > 0)
posts = (Post[])postObjectArray;
if (posts == null)
{
Log.d("Problems", "posts is null");
}
}
}
Post class:
public class Post extends Object{
private BackendlessUser user;
private String file;
private String message;
private String objectId;
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public Post() {
}
public Post(BackendlessUser user, String file, String message) {
this.user = user;
this.file = file;
this.message = message;
}
public BackendlessUser getUser() {
return user;
}
public void setUser(BackendlessUser user) {
this.user = user;
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
The reason was that checkbox "autoload", which loads all values of one to many relationships to which checkbox corresponds when the owning table was loaded, wasn't checked. Thus, user.getProperty("posts") was always returning null. But it should be noted that if the row is data object relationship, it returns HashMap<String, Object>, so Post needs to have constructor which accepts HashMap<String, Object>:
public Post(HashMap hashMap)
{
this.hashMap = hashMap;
property1 = (String)hashMap.get("property1");
/*other properties*/
}
If there is no values, it will return an Object, which is not castable to any other type, so the way used in the docs must be applied in order to fix edge cases.
EDIT:
It's useful to map data table to class before making any calls to Backendless. That way, it is possible to eliminate all work with HashMap and directly get desired type. Despite that, if you're retrieving data through Backendless.Persistence you can specify it as argument.

Categories