/* * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.libraries.remixer; import com.google.android.libraries.remixer.serialization.StoredVariable; import java.lang.ref.WeakReference; /** * Base class for all Remixes that does not do any value checking. A variable takes care of calling * a callback when the value is changed. It does not support any sort of null values. * * <p><b>This class is not thread-safe and should only be used from the main thread.</b> */ public class Variable<T> { /** * The name to display in the UI for this variable. */ private final String title; /** * The key to use to identify this item across storage and all the interfaces. */ private final String key; /** * The layout to inflate to display this variable. If set to 0, the default layout associated * with the variable type will be used. */ private final int layoutId; /** * A weak reference to this RemixerItem's context. The RemixerItem's lifecycle is tied to its * contexts'. * * <p>It should be a reference to an activity, but it isn't since remixer_core cannot depend on * Android classes. It is a weak reference in order not to leak the activity accidentally. */ private final WeakReference<Object> context; /** * The remixer instance this RemixerItem has been attached to. */ protected Remixer remixer; /** * The data type held in this RemixerItem. */ private DataType dataType; /** * The callback to be executed when the value is updated. */ private Callback<T> callback; /** * Creates a new Variable. * * @param key The key to use to save to SharedPreferences. This needs to be unique across all * Remixes. * @param title The name to display in the UI. * @param initialValue The initial value for this Variable. * @param context the object which created this variable, should be an activity. * @param callback A callback to execute when the value is updated. Can be {@code null}. * @param layoutId A layout to inflate when displaying this Variable in the UI. * @param dataType The data type this variable contains. */ protected Variable( String title, String key, T initialValue, Object context, Callback<T> callback, int layoutId, DataType dataType) { this.title = title; this.key = key; this.context = new WeakReference<>(context); this.layoutId = layoutId; this.dataType = dataType; this.selectedValue = initialValue; this.callback = callback; } /** * Makes sure the initial value is valid for this variable and runs the callback if so. This must * be called as soon as the Variable is created. * * @throws IllegalArgumentException The currently selected value (or initial value) is invalid for * this Variable. See {@link #checkValue(Object)}. */ public final void init() { checkValue(selectedValue); runCallback(); } public DataType getDataType() { return dataType; } public String getTitle() { return title; } public String getKey() { return key; } /** * Returns the layout id to inflate when displaying this variable. */ public int getLayoutId() { return layoutId; } /** * Returns the context. */ Object getContext() { return context.get(); } /** * Set the current remixer instance. This allows the variable to notify other variables with the * same key. */ public void setRemixer(Remixer remixer) { this.remixer = remixer; } /** * The currently selected value. */ private T selectedValue; public T getSelectedValue() { return selectedValue; } /** * Checks that the value passed in is a valid value, otherwise throws {@link * IllegalArgumentException}. * * @throws IllegalArgumentException An invalid value was passed in. */ protected void checkValue(T value) { // No need to check anything in the base class. } /** * Sets the selected value to a new value. * * <p>This also notifies all other variables with the same key that the value has changed. * * @param newValue Value to set. Cannot be null. * @throws IllegalArgumentException {@code newValue} is an invalid value for this Variable. */ public void setValue(T newValue) { setValueWithoutNotifyingOthers(newValue); setValueOnOthersWithTheSameKey(); } /** * Sets the selected value to a new value without notifying other variables of this change. * <b>Only for internal use!!</b> * * @param newValue Value to set. Cannot be null. * @throws IllegalArgumentException {@code newValue} is an invalid value for this Variable. */ public void setValueWithoutNotifyingOthers(T newValue) { checkValue(newValue); selectedValue = newValue; runCallback(); } /** * Gets the serializable constraints string for this variable. */ public String getSerializableConstraints() { return StoredVariable.VARIABLE_CONSTRAINT; } /** * Sets the new value on all other Variables of the same key. */ @SuppressWarnings("unchecked") private void setValueOnOthersWithTheSameKey() { if (remixer == null) { // This instance hasn't been added to a Remixer, probably still being set up, abort. return; } remixer.onValueChanged(this); } private void runCallback() { if (callback != null) { callback.onValueSet(this); } } /** * Convenience builder for Variable. * * <p>This builder assumes a few things for your convenience: * <ul> * <li>If the layout id is not set, the default layout will be used. * <li>If the title is not set, the key will be used as title * </ul> * * <p>On the other hand: key, dataType, and context are mandatory. If they're missing, an * {@link IllegalArgumentException} will be thrown. */ public static class Builder<T> extends BaseVariableBuilder<Variable<T>, T> { @Override public Variable<T> build() { checkBaseFields(); Variable<T> variable = new Variable<T>(title, key, initialValue, context, callback, layoutId, dataType); variable.init(); return variable; } public Builder() {} } }