diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/data/local/DbLightDao.java b/app/src/main/java/org/d3kad3nt/sunriseClock/data/local/DbLightDao.java index fadd6840..50134cf7 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/data/local/DbLightDao.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/data/local/DbLightDao.java @@ -54,12 +54,13 @@ default void upsert(DbLight obj) { // Case 3 // Primary key NOT found in light object. This can happen if (all) light objects are retrieved from the // remote endpoint. - // Rhe lightId primary key is autogenerated by room, therefore it is not known to the remote endpoint. + // The lightId primary key is autogenerated by room, therefore it is not known to the remote endpoint. else if (obj.getEndpointId() != 0L && !(obj.getEndpointLightId().equals(""))) { int rowsUpdated = updateUsingEndpointIdAndEndpointLightId(obj.getEndpointId(), obj.getEndpointLightId(), obj.getName(), obj.getIsSwitchable(), obj.getIsOn(), obj.getIsDimmable(), obj.getBrightness(), - obj.getIsTemperaturable(), obj.getColorTemperature(), obj.getIsColorable(), obj.getColor()); + obj.getIsTemperaturable(), obj.getColorTemperature(), obj.getIsColorable(), obj.getColor(), + obj.getIsReachable()); Log.d(TAG, rowsUpdated + " rows updated by room. Updated DbLight with endpointId: " + obj.getEndpointId() + " and endpointLightId: " + obj.getEndpointLightId()); @@ -100,12 +101,12 @@ else if (obj.getEndpointId() != 0L && !(obj.getEndpointLightId().equals(""))) { */ @Query("UPDATE " + DbLight.TABLENAME + " SET name = :friendlyName, is_switchable = :switchable, is_on = :on, " + "is_dimmable = :dimmable, brightness = :brightness, is_temperaturable = :temperaturable, " + - "colortemperature = :colorTemperature, is_colorable = :colorable, color = :color WHERE endpoint_id = " + - ":endpointId AND endpoint_light_id = :endpointLightId") + "colortemperature = :colorTemperature, is_colorable = :colorable, color = :color, is_reachable = " + + ":is_reachable WHERE endpoint_id = " + ":endpointId AND endpoint_light_id = :endpointLightId") int updateUsingEndpointIdAndEndpointLightId(long endpointId, String endpointLightId, String friendlyName, boolean switchable, boolean on, boolean dimmable, int brightness, boolean temperaturable, int colorTemperature, boolean colorable, - int color); + int color, boolean is_reachable); @Delete() void delete(DbLight obj); diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/DbLightBuilder.java b/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/DbLightBuilder.java index e6846483..cb37c2ef 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/DbLightBuilder.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/DbLightBuilder.java @@ -28,6 +28,24 @@ public class DbLightBuilder { /** * Builder for constructing DbLights. */ + + public static DbLightBuilder from(DbLight dbLight) { + DbLightBuilder builder = new DbLightBuilder(); + builder.setEndpointId(dbLight.getEndpointId()); + builder.setEndpointLightId(dbLight.getEndpointLightId()); + builder.setName(dbLight.getName()); + builder.setIsSwitchable(dbLight.getIsSwitchable()); + builder.setIsOn(dbLight.getIsOn()); + builder.setIsDimmable(dbLight.getIsDimmable()); + builder.setBrightness(dbLight.getBrightness()); + builder.setIsTemperaturable(dbLight.getIsTemperaturable()); + builder.setColorTemperature(dbLight.getColorTemperature()); + builder.setIsColorable(dbLight.getIsColorable()); + builder.setColor(dbLight.getColor()); + builder.setIsReachable(dbLight.getIsReachable()); + return builder; + } + public DbLightBuilder() { } diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/UILight.java b/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/UILight.java index a2707f3e..676308bc 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/UILight.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/data/model/light/UILight.java @@ -53,7 +53,6 @@ private UILight(long lightId, long endpointId, String name, boolean isSwitchable @NonNull @Contract("_ -> new") public static UILight from(@NonNull DbLight dbLight) { - Log.d(TAG, "Converting DbLight to UiLight..."); // Place for conversion logic (if UI needs other data types or value ranges). UILight uiLight = new UILight(dbLight.getLightId(), dbLight.getEndpointId(), dbLight.getName(), dbLight.getIsSwitchable(), diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/LightRepository.java b/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/LightRepository.java index 3573c65d..d63d2ec9 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/LightRepository.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/LightRepository.java @@ -1,6 +1,7 @@ package org.d3kad3nt.sunriseClock.data.repository; import android.content.Context; +import android.util.Log; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -163,6 +164,8 @@ protected UILight convertDbTypeToResultType(DbLight item) { @Override protected DbLight convertRemoteTypeToDbType(ApiSuccessResponse response) { + Log.d(TAG, "Convert: Light " + response.getBody().getName() + " is reachable: " + + response.getBody().getIsReachable()); return DbLight.from(response.getBody()); } }; diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/NetworkBoundResource.java b/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/NetworkBoundResource.java index bfc3fed4..0663b363 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/NetworkBoundResource.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/data/repository/NetworkBoundResource.java @@ -32,11 +32,11 @@ /** * A generic class that can provide a resource backed by both the sqlite database and the network. Copied from the - * official Google architecture-components github-sample under https://github - * .com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android - * /example/github/repository/NetworkBoundResource.kt + * official Google architecture-components github-sample under NetworkBoundResource Source * - * You can read more about it in the [Architecture Guide](https://developer.android.com/arch). + * You can read more about it in the Architecture Guide */ public abstract class NetworkBoundResource extends ExtendedMediatorLiveData> { @@ -60,6 +60,9 @@ private void dbSourceObserver(DbType data, LiveData dbSourceLiveData) { dbObject = data; removeSource(dbSourceLiveData); if (shouldFetch(dbObject)) { + addSource(dbSource, newData -> { + updateValue(Resource.success(convertDbTypeToResultType(newData))); + }); LiveData endpointLiveData = loadEndpoint(); addSource(endpointLiveData, baseEndpoint -> { endpointLiveDataObserver(baseEndpoint, endpointLiveData); @@ -73,9 +76,7 @@ private void dbSourceObserver(DbType data, LiveData dbSourceLiveData) { } private void endpointLiveDataObserver(BaseEndpoint endpoint, LiveData endpointLiveData) { - if (endpoint == null) { - updateValue(Resource.loading(null)); - } else { + if (endpoint != null) { this.endpoint = endpoint; fetchFromNetwork(dbSource); removeSource(endpointLiveData); @@ -85,14 +86,10 @@ private void endpointLiveDataObserver(BaseEndpoint endpoint, LiveData dbSource) { LiveData> apiResponse = loadFromNetwork(); // we re-attach dbSource as a new source, it will dispatch its latest value quickly - addSource(dbSource, newData -> { - updateValue(Resource.loading(convertDbTypeToResultType(newData))); - }); addSource(apiResponse, response -> { removeSource(apiResponse); removeSource(dbSource); - Class aClass = response.getClass(); - if (ApiSuccessResponse.class.equals(aClass)) { + if (response instanceof ApiSuccessResponse) { ServiceLocator.getExecutor(ExecutorType.IO).execute(() -> { saveResponseToDb(convertRemoteTypeToDbType((ApiSuccessResponse) response)); ServiceLocator.getExecutor(ExecutorType.MainThread).execute(() -> { @@ -105,14 +102,14 @@ private void fetchFromNetwork(LiveData dbSource) { }); }); }); - } else if (ApiEmptyResponse.class.equals(aClass)) { + } else if (response instanceof ApiEmptyResponse) { ServiceLocator.getExecutor(ExecutorType.MainThread).execute(() -> { // reload from disk whatever we had addSource(loadFromDb(), newData -> { updateValue(Resource.success(convertDbTypeToResultType(newData))); }); }); - } else if (ApiErrorResponse.class.equals(aClass)) { + } else if (response instanceof ApiErrorResponse) { onFetchFailed(); addSource(dbSource, newData -> { updateValue(Resource.error(((ApiErrorResponse) response).getErrorMessage(), diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/deviceControl/ControlService.java b/app/src/main/java/org/d3kad3nt/sunriseClock/deviceControl/ControlService.java index f743127d..af3585bc 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/deviceControl/ControlService.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/deviceControl/ControlService.java @@ -31,6 +31,7 @@ import org.d3kad3nt.sunriseClock.data.model.resource.Status; import org.d3kad3nt.sunriseClock.data.repository.EndpointRepository; import org.d3kad3nt.sunriseClock.data.repository.LightRepository; +import org.d3kad3nt.sunriseClock.util.Action; import org.d3kad3nt.sunriseClock.util.AsyncJoin; import org.d3kad3nt.sunriseClock.util.ExtendedPublisher; import org.d3kad3nt.sunriseClock.util.LiveDataUtil; @@ -77,7 +78,7 @@ public Flow.Publisher createPublisherForAllAvailable() { LiveData> allEndpoints = getEndpointRepository().getAllEndpoints(); AsyncJoin asyncHelper = new AsyncJoin(); - LiveDataUtil.observeOnce(allEndpoints, new AsyncJoin.Observer<>(asyncHelper) { + allEndpoints.observeForever(new AsyncJoin.Observer<>(asyncHelper) { @Override public void onChanged(final List endpoints) { for (IEndpointUI endpoint : endpoints) { @@ -106,6 +107,7 @@ public void onChanged(final Resource> listResource) { }); } asyncHelper.removeAsyncTask(this); + allEndpoints.removeObserver(this); } }); asyncHelper.executeWhenJoined(() -> flow.complete()); @@ -308,9 +310,9 @@ private Context getNonNullBaseContext() { } private void initEndpointNames() { - LiveDataUtil.observeOnce(getEndpointRepository().getAllEndpoints(), new Observer<>() { + LiveDataUtil.observeOnce(getEndpointRepository().getAllEndpoints(), new Action<>() { @Override - public void onChanged(final List iEndpointUIS) { + public void execute(final List iEndpointUIS) { for (IEndpointUI endpoint : iEndpointUIS) { endpointNames.put(endpoint.getId(), endpoint.getStringRepresentation()); } diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/LightsFragment.java b/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/LightsFragment.java index c90e5e6e..ab1289e4 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/LightsFragment.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/LightsFragment.java @@ -1,5 +1,6 @@ package org.d3kad3nt.sunriseClock.ui.light; +import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -19,6 +20,8 @@ import org.d3kad3nt.sunriseClock.data.model.resource.Resource; import org.d3kad3nt.sunriseClock.data.model.resource.Status; import org.d3kad3nt.sunriseClock.databinding.LightsFragmentBinding; +import org.d3kad3nt.sunriseClock.ui.MainActivity; +import org.jetbrains.annotations.Contract; import java.util.Collections; import java.util.Comparator; @@ -46,8 +49,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @Override public void onChanged(Resource> listResource) { Log.d(TAG, listResource.getStatus().toString()); - if (listResource.getStatus().equals(Status.SUCCESS) && listResource.getData() != null) { - lightsState.clearError(); + if (listResource.getData() != null){ List list = listResource.getData(); Collections.sort(list, new Comparator<>() { @Override @@ -56,11 +58,17 @@ public int compare(final UILight uiLight, final UILight uiLight2) { } }); adapter.submitList(list); - } else if (listResource.getStatus().equals(Status.ERROR)) { - lightsState.setError(getResources().getString(R.string.noLights_title), - listResource.getMessage()); + } else { adapter.submitList(List.of()); } + switch (listResource.getStatus()){ + case SUCCESS: + lightsState.clearError(); + break; + case ERROR: + String errorMsg = getResources().getString(R.string.noLights_title); + lightsState.setError(errorMsg, listResource.getMessage()); + } } }); diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/lightDetail/LightDetailViewModel.java b/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/lightDetail/LightDetailViewModel.java index 150c2481..09b067fb 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/lightDetail/LightDetailViewModel.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/ui/light/lightDetail/LightDetailViewModel.java @@ -18,6 +18,7 @@ import org.d3kad3nt.sunriseClock.ui.util.BooleanVisibilityLiveData; import org.d3kad3nt.sunriseClock.ui.util.ResourceVisibilityLiveData; import org.d3kad3nt.sunriseClock.util.LiveDataUtil; +import org.jetbrains.annotations.Contract; import kotlin.jvm.functions.Function1; @@ -69,14 +70,19 @@ private LiveData> getLight(long lightID) { return lightRepository.getLight(lightID); } + @NonNull + @Contract(" -> new") private LiveData getIsReachable() { return Transformations.map(light, new Function1<>() { @Override public Boolean invoke(final Resource input) { - if (input.getStatus() == Status.SUCCESS) { + if (input.getStatus() == Status.ERROR) { + return false; + } + if (input.getData() != null) { return input.getData().getIsReachable(); } - return true; + return false; } }); } diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/util/Action.java b/app/src/main/java/org/d3kad3nt/sunriseClock/util/Action.java new file mode 100644 index 00000000..97dfebc0 --- /dev/null +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/util/Action.java @@ -0,0 +1,7 @@ +package org.d3kad3nt.sunriseClock.util; + +@FunctionalInterface +public interface Action { + + void execute(final T t); +} diff --git a/app/src/main/java/org/d3kad3nt/sunriseClock/util/LiveDataUtil.java b/app/src/main/java/org/d3kad3nt/sunriseClock/util/LiveDataUtil.java index f1a03746..11142df0 100644 --- a/app/src/main/java/org/d3kad3nt/sunriseClock/util/LiveDataUtil.java +++ b/app/src/main/java/org/d3kad3nt/sunriseClock/util/LiveDataUtil.java @@ -2,25 +2,39 @@ import android.util.Log; +import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; public class LiveDataUtil { - public static void logChanges(String TAG, LiveData liveData) { + public static void logChanges(final String TAG, @NonNull final LiveData liveData) { liveData.observeForever(t -> { Log.d("LiveDataUtil", "Log Change"); Log.d(TAG, t.toString()); }); } - public static void observeOnce(LiveData liveData, Observer observer) { + public static void observeOnce(@NonNull final LiveData liveData, @NonNull final Action action) { liveData.observeForever(new Observer() { @Override public void onChanged(T t) { - observer.onChanged(t); + action.execute(t); liveData.removeObserver(this); } }); } + + public static void observeUntilNotNull(@NonNull final LiveData light, @NonNull final Action action) { + light.observeForever(new Observer() { + @Override + public void onChanged(final T t) { + if (t == null) { + return; + } + light.removeObserver(this); + action.execute(t); + } + }); + } }