Table Square Flow + ejemplos de tabletas de mortero

He estado experimentando con el uso de flujo y mortero como una arquitectura alternativa para nuestras aplicaciones de Android. He estado trabajando en una aplicación que en el minuto es sólo un diseño de teléfono único, pero me preguntaba cómo la arquitectura de flujo y mortero podría funcionar si desea tener un diseño diferente para las tabletas. Los detalles maestros pueden ser el ejemplo más simple, pero obviamente hay otros ejemplos.

Tengo algunas ideas de cómo esto podría funcionar, pero quería saber lo que los desarrolladores cuadrados podría haber pensado ya alrededor de este tema.

One Solution collect form web for “Table Square Flow + ejemplos de tabletas de mortero”

Todavía estamos trabajando en una respuesta canónica para esto, pero la idea básica es que deje que el sistema de recursos cambie las vistas que está mostrando en qué situación. Por lo tanto, su actividad establece su vista de contenido, por ejemplo, en R.layout.root_view . La versión de tablet de ese diseño (lo ponemos en res/layout-sw600dp ) se puede vincular a diferentes vistas, lo que podría inyectar diferentes presentadores, y así sucesivamente.

Para los casos en los que es necesario tomar una decisión en tiempo de ejecución, defina un recurso booleano en values/bools .xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <bool name="show_tablet_ui">false</bool> </resources> 

Y values-sw600dp/bools.xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <bool name="show_tablet_ui">true</bool> </resources> 

Exponerlo al resto de la aplicación a través de la daga. Utilice esta anotación de enlace:

 /** * Whether we should show a tablet UI. */ @Retention(RUNTIME) @Qualifier public @interface ShowTabletUi { int ID = R.bool.show_tablet_ui; } 

Y un método de proveedor como:

 /** * Singleton because there's no reason to read it from resources again, * it won't change. */ @Provides @ShowTabletUi @Singleton boolean showTabletUi(Resources resources) { return resources.getBoolean(ShowTabletUi.ID); } 

¡Pero espera hay mas! Suponga que desea tener una sola definición de pantalla / plano que fabrique diferentes módulos para diferentes factores de forma. Hemos empezado a usar un esquema de anotaciones para simplificar ese tipo de cosas. En lugar de hacer que nuestras clases de pantalla implementen BluePrint , hemos empezado a usar algunas anotaciones para declarar su clase de interfaz. En ese mundo aquí es cómo una pantalla puede elegir selectivamente qué módulos utilizar para tableta o móvil.

 @Layout(R.layout.some_view) @WithModuleFactory(SomeScreen.ModuleFactory.class) public class SomeScreen { public static class ModuleFactory extends ResponsiveModuleFactory<HomeScreen> { @Override protected Object createTabletModule(HomeScreen screen) { return new TabletModule(); } @Override protected Object createMobileModule(HomeScreen screen) { return new MobileModule(); } } 

Magia, ¿verdad? Esto es lo que hay detrás de la cortina. En primer lugar, un ModuleFactory es una clase estática que se da acceso a la pantalla y los recursos y escupe un módulo dagger.

 public abstract class ModuleFactory<T> { final Blueprint createBlueprint(final Resources resources, final MortarScreen screen) { return new Blueprint() { @Override public String getMortarScopeName() { return screen.getName(); } @Override public Object getDaggerModule() { return ModuleFactory.this.createDaggerModule(resources, (T) screen); } }; } protected abstract Object createDaggerModule(Resources resources, T screen); } 

Nuestra subclase trixie ResponsiveModuleFactory parece a esto. (¿Recuerdas cómo ShowTabletUi.java definió la identificación del recurso como una constante?

 public abstract class ResponsiveModuleFactory<T> extends ModuleFactory<T> { @Override protected final Object createDaggerModule(Resources resources, T screen) { boolean showTabletUi = resources.getBoolean(ShowTabletUi.ID); return showTabletUi ? createTabletModule(screen) : createMobileModule(screen); } protected abstract Object createTabletModule(T screen); protected abstract Object createMobileModule(T screen); } 

Para hacer todo esto ir, tenemos una clase ScreenScoper (abajo). En el código de muestra de Mortero, haría que el ScreenConductor usara uno de estos para crear y destruir ámbitos. Tarde o temprano (pronto espero) Mortero y / o sus muestras serán actualizadas para incluir estas cosas.

 package mortar; import android.content.Context; import android.content.res.Resources; import com.squareup.util.Objects; import dagger.Module; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.LinkedHashMap; import java.util.Map; import static java.lang.String.format; /** * Creates {@link MortarScope}s for screens that may be annotated with {@link WithModuleFactory}, * {@link WithModule} or {@link Module}. */ public class ScreenScoper { private static final ModuleFactory NO_FACTORY = new ModuleFactory() { @Override protected Object createDaggerModule(Resources resources, Object screen) { throw new UnsupportedOperationException(); } }; private final Map<Class, ModuleFactory> moduleFactoryCache = new LinkedHashMap<>(); public MortarScope getScreenScope(Context context, final MortarScreen screen) { MortarScope parentScope = Mortar.getScope(context); return getScreenScope(context.getResources(), parentScope, screen); } /** * Finds or creates the scope for the given screen, honoring its optoinal {@link * WithModuleFactory} or {@link WithModule} annotation. Note the scopes are also created * for unannotated screens. */ public MortarScope getScreenScope(Resources resources, MortarScope parentScope, final MortarScreen screen) { ModuleFactory moduleFactory = getModuleFactory(screen); MortarScope childScope; if (moduleFactory != NO_FACTORY) { Blueprint blueprint = moduleFactory.createBlueprint(resources, screen); childScope = parentScope.requireChild(blueprint); } else { // We need every screen to have a scope, so that anything it injects is scoped. We need // this even if the screen doesn't declare a module, because Dagger allows injection of // objects that are annotated even if they don't appear in a module. Blueprint blueprint = new Blueprint() { @Override public String getMortarScopeName() { return screen.getName(); } @Override public Object getDaggerModule() { return null; } }; childScope = parentScope.requireChild(blueprint); } return childScope; } private ModuleFactory getModuleFactory(MortarScreen screen) { Class<?> screenType = Objects.getClass(screen); ModuleFactory moduleFactory = moduleFactoryCache.get(screenType); if (moduleFactory != null) return moduleFactory; WithModule withModule = screenType.getAnnotation(WithModule.class); if (withModule != null) { Class<?> moduleClass = withModule.value(); Constructor<?>[] constructors = moduleClass.getDeclaredConstructors(); if (constructors.length != 1) { throw new IllegalArgumentException( format("Module %s for screen %s should have exactly one public constructor", moduleClass.getName(), screen.getName())); } Constructor constructor = constructors[0]; Class[] parameters = constructor.getParameterTypes(); if (parameters.length > 1) { throw new IllegalArgumentException( format("Module %s for screen %s should have 0 or 1 parameter", moduleClass.getName(), screen.getName())); } Class screenParameter; if (parameters.length == 1) { screenParameter = parameters[0]; if (!screenParameter.isInstance(screen)) { throw new IllegalArgumentException(format("Module %s for screen %s should have a " + "constructor parameter that is a super class of %s", moduleClass.getName(), screen.getName(), screen.getClass().getName())); } } else { screenParameter = null; } try { if (screenParameter == null) { moduleFactory = new NoArgsFactory(constructor); } else { moduleFactory = new SingleArgFactory(constructor); } } catch (Exception e) { throw new RuntimeException( format("Failed to instantiate module %s for screen %s", moduleClass.getName(), screen.getName()), e); } } if (moduleFactory == null) { WithModuleFactory withModuleFactory = screenType.getAnnotation(WithModuleFactory.class); if (withModuleFactory != null) { Class<? extends ModuleFactory> mfClass = withModuleFactory.value(); try { moduleFactory = mfClass.newInstance(); } catch (Exception e) { throw new RuntimeException(format("Failed to instantiate module factory %s for screen %s", withModuleFactory.value().getName(), screen.getName()), e); } } } if (moduleFactory == null) moduleFactory = NO_FACTORY; moduleFactoryCache.put(screenType, moduleFactory); return moduleFactory; } private static class NoArgsFactory extends ModuleFactory<Object> { final Constructor moduleConstructor; private NoArgsFactory(Constructor moduleConstructor) { this.moduleConstructor = moduleConstructor; } @Override protected Object createDaggerModule(Resources resources, Object ignored) { try { return moduleConstructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } private static class SingleArgFactory extends ModuleFactory { final Constructor moduleConstructor; public SingleArgFactory(Constructor moduleConstructor) { this.moduleConstructor = moduleConstructor; } @Override protected Object createDaggerModule(Resources resources, Object screen) { try { return moduleConstructor.newInstance(screen); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } } 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.