I have a widget which I want to update every time I update my database and want to show the content from the database on the widget even when the app is not running.
My db
is implemented using Room
, and the widget is updated using an
IntentService
. Everytime an update is made, onHandleIntent
will try to get the new entry from the db and update the widget.
But for the code below, it always returns a null List
from the db
Is there an alternate way to implement this? AsyncTask
wont work here as it requires execution on main thread.
I am new to Room
and RxJava
, so not sure if I missed anything obvious.
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null || intent.getAction() == null) return;
if (intent.getAction().equals(UPDATE_WIDGET)) {
Context context = getApplicationContext();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this, ParkWidgetProvider.class));
Observable.fromCallable(() -> {
db = FavDatabase.getInstance(context);
return db;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
List<FavParkEntity> favParkEntityList = result.favDoa().getFavPark().getValue();
if (favParkEntityList != null) {
if (favParkEntityList.size() == 1) {
FavParkEntity favParkEntity = favParkEntityList.get(0);
latLong = favParkEntity.getLatLong();
parkCode = favParkEntity.getParkCode();
imgUrl = favParkEntity.getImage();
title = favParkEntity.getPark_name();
Log.e(TAG, "onHandleIntent: HERE: " + title);
StringToGPSCoordinates stringToGPSCoordinates = new StringToGPSCoordinates();
final String gpsCoodinates[] = stringToGPSCoordinates.convertToGPS(latLong);
getLastLocation(gpsCoodinates);
String weatherDetails[] = getCurrentWeather(context, gpsCoodinates);
ParkWidgetProvider.updateAppWidgets(context, appWidgetManager, appWidgetIds, parkCode, imgUrl, title, weatherDetails, distance);
} else {
Log.e(TAG, "onHandleIntent: HERE SIZE");
}
} else {
Log.e(TAG, "onHandleIntent: HERE NULL");
}
});
}
}
Database:
@Database(entities = {FavParkEntity.class},version = 1, exportSchema = false)
@TypeConverters({Converters.class})
public abstract class FavDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "favorites";
private static final Object LOCK = new Object();
private static FavDatabase sInstance;
public static FavDatabase getInstance(Context context){
if(sInstance==null){
synchronized (LOCK){
sInstance = Room.databaseBuilder(context.getApplicationContext(),FavDatabase.class, FavDatabase.DATABASE_NAME)
.build();
}
}
return sInstance;
}
public RoomDatabase dbExist(){
return sInstance;
}
public abstract FavDao favDoa();
}
Dao:
@Dao
public interface FavDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void save(FavParkEntity park);
@Query("SELECT * FROM favorites")
LiveData<List<FavParkEntity>> getFavPark();
@Query("DELETE FROM favorites")
void clearTable();
}
Model:
@Entity(tableName = "favorites")
public class FavParkEntity extends ParkEntity {
public FavParkEntity() {
}
public FavParkEntity (ParkEntity parkEntity){
this.setPark_id(parkEntity.getPark_id());
this.setPark_name(parkEntity.getPark_name());
this.setStates(parkEntity.getStates());
this.setParkCode(parkEntity.getParkCode());
this.setLatLong(parkEntity.getLatLong());
this.setDescription(parkEntity.getDescription());
this.setDesignation(parkEntity.getDesignation());
this.setAddress(parkEntity.getAddress());
this.setPhone(parkEntity.getPhone());
this.setEmail(parkEntity.getEmail());
this.setImage(parkEntity.getImage());
}
}
There are many things wrong with the implementation.
First of all, result.favDoa().getFavPark().getValue()
will always return null because LiveData
is asynchronous. LiveData
is meant to be observed.
Also the way you use RxJava
seems incorrect. You are not doing any real job asynchronously. That implementation can be written without using RxJava
at all.
Also note that there isn't an easy way to use LiveData
AND RxJava
together because they are not directly interchangeable. People likes to go either "full LiveData
", "full RxJava
", or "RxJava
until UI components then switch to LiveData
.
There are many different ways to solve this. One option is to change your implementation to RxJava completely. (Room has RxJava support: https://developer.android.com/topic/libraries/architecture/adding-components#room)
Dao:
@Dao
public interface FavDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void save(FavParkEntity park);
@Query("SELECT * FROM favorites")
Single<List<FavParkEntity>> getFavPark(); // Switch to RxJava
@Query("DELETE FROM favorites")
void clearTable();
}
Your main code:
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null || intent.getAction() == null) return;
if (intent.getAction().equals(UPDATE_WIDGET)) {
Context context = getApplicationContext();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this, ParkWidgetProvider.class));
FavDatabase.getInstance(context).favDoa().getFavPark() // Access database here.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(favParkEntityList -> {
// Put your mainThread works here.
if (favParkEntityList != null) {
if (favParkEntityList.size() == 1) {
FavParkEntity favParkEntity = favParkEntityList.get(0);
latLong = favParkEntity.getLatLong();
parkCode = favParkEntity.getParkCode();
imgUrl = favParkEntity.getImage();
title = favParkEntity.getPark_name();
Log.e(TAG, "onHandleIntent: HERE: " + title);
...
} else {
Log.e(TAG, "onHandleIntent: HERE SIZE");
}
} else {
Log.e(TAG, "onHandleIntent: HERE NULL");
}
});
}
}
Optionally, if you can't switch your Dao from LiveData
to RxJava
, you can create synchronous version of getFavPark()
.
Dao:
@Dao
public interface FavDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void save(FavParkEntity park);
@Query("SELECT * FROM favorites")
LiveData<List<FavParkEntity>> getFavPark();
@Query("SELECT * FROM favorites")
List<FavParkEntity> getFavParkSync(); // Synchronous access
@Query("DELETE FROM favorites")
void clearTable();
}
Your main code:
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null || intent.getAction() == null) return;
if (intent.getAction().equals(UPDATE_WIDGET)) {
Context context = getApplicationContext();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this, ParkWidgetProvider.class));
Observable.fromCallable(() -> {
// fromCallable and subscribeOn will make
// below code run in a worker thread asynchronously.
return FavDatabase.getInstance(context).favDoa().getFavParkSync();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(favParkEntityList -> {
// Put your mainThread works here.
if (favParkEntityList != null) {
if (favParkEntityList.size() == 1) {
FavParkEntity favParkEntity = favParkEntityList.get(0);
latLong = favParkEntity.getLatLong();
parkCode = favParkEntity.getParkCode();
imgUrl = favParkEntity.getImage();
title = favParkEntity.getPark_name();
Log.e(TAG, "onHandleIntent: HERE: " + title);
...
} else {
Log.e(TAG, "onHandleIntent: HERE SIZE");
}
} else {
Log.e(TAG, "onHandleIntent: HERE NULL");
}
});
}
}