androidandroid-widgetrx-javaandroid-roomandroid-intentservice

Access Room database to update widget in onHandleIntent method of IntentService


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());
    }

}

Solution

  • 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");
                            }
                       });
            }
        }