package me.tatarka.bindingcollectionadapter2; import android.content.Context; import android.content.res.Resources; import android.databinding.ViewDataBinding; import android.os.Looper; import android.support.annotation.LayoutRes; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Helper databinding utilities. May be made public some time in the future if they prove to be * useful. */ class Utils { private static final String TAG = "BCAdapters"; /** * Helper to throw an exception when {@link android.databinding.ViewDataBinding#setVariable(int, * Object)} returns false. */ static void throwMissingVariable(ViewDataBinding binding, int bindingVariable, @LayoutRes int layoutRes) { Context context = binding.getRoot().getContext(); Resources resources = context.getResources(); String layoutName = resources.getResourceName(layoutRes); // Yeah reflection is slow, but this only happens when there is a programmer error. String bindingVariableName; try { bindingVariableName = getBindingVariableName(context, bindingVariable); } catch (Resources.NotFoundException e) { // Fall back to int bindingVariableName = "" + bindingVariable; } throw new IllegalStateException("Could not bind variable '" + bindingVariableName + "' in layout '" + layoutName + "'"); } /** * Returns the name for the given binding variable int. Warning! This uses reflection so it * should <em>only</em> be used for debugging. * * @throws Resources.NotFoundException if the name cannot be found. */ static String getBindingVariableName(Context context, int bindingVariable) throws Resources.NotFoundException { try { return getBindingVariableByDataBinderMapper(bindingVariable); } catch (Exception e1) { try { return getBindingVariableByBR(context, bindingVariable); } catch (Exception e2) { throw new Resources.NotFoundException("" + bindingVariable); } } } /** * Attempt to get the name from a non-public method on the generated DataBinderMapper class. * This method does exactly what we want, but who knows if it will be there in future versions. */ private static String getBindingVariableByDataBinderMapper(int bindingVariable) throws Exception { Class<?> dataBinderMapper = Class.forName("android.databinding.DataBinderMapper"); Method convertIdMethod = dataBinderMapper.getDeclaredMethod("convertBrIdToString", int.class); convertIdMethod.setAccessible(true); Constructor constructor = dataBinderMapper.getDeclaredConstructor(); constructor.setAccessible(true); Object instance = constructor.newInstance(); Object result = convertIdMethod.invoke(instance, bindingVariable); return (String) result; } /** * Attempt to get the name by using reflection on the generated BR class. Unfortunately, we * don't know BR's package name so this may fail if it's not the same as the apps package name. */ private static String getBindingVariableByBR(Context context, int bindingVariable) throws Exception { String packageName = context.getPackageName(); Class BRClass = Class.forName(packageName + ".BR"); Field[] fields = BRClass.getFields(); for (Field field : fields) { int value = field.getInt(null); if (value == bindingVariable) { return field.getName(); } } throw new Exception("not found"); } /** * Ensures the call was made on the main thread. This is enforced for all ObservableList change * operations. */ static void ensureChangeOnMainThread() { if (Thread.currentThread() != Looper.getMainLooper().getThread()) { throw new IllegalStateException("You must only modify the ObservableList on the main thread."); } } /** * Constructs a binding adapter class from it's class name using reflection. */ @SuppressWarnings("unchecked") static <T, A extends BindingCollectionAdapter<T>> A createClass(Class<? extends BindingCollectionAdapter> adapterClass, ItemBinding<T> itemBinding) { try { return (A) adapterClass.getConstructor(ItemBinding.class).newInstance(itemBinding); } catch (Exception e1) { throw new RuntimeException(e1); } } }