问题 方形流动+砂浆片例子


我一直在尝试使用flow和mortar作为Android应用程序的替代架构。我一直在研究一个应用程序,它在一分钟只有一个电话布局,但我想知道如果你想为平板电脑设置不同的流量和灰浆架构如何工作。主要细节可能是最简单的例子,但显然还有其他例子。

我有一些想法如何工作,但我想知道广场开发人员可能已经考虑过这个主题。


4074
2018-03-24 17:37


起源



答案:


我们仍然在为此做一个规范的答案,但基本的想法是你让资源系统改变你在哪种情况下显示的观点。因此,您的活动会将其内容视图设置为,例如, R.layout.root_view。该布局的平板电脑版本(我们把它放入 res/layout-sw600dp)可以绑定到不同的视图,这可能会注入不同的演示者,等等。

对于需要做出运行时决策的情况,请在中定义一个布尔资源 values/bools .xml

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

values-sw600dp/bools.xml

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

通过匕首将其暴露给应用程序的其余部分。使用此绑定注释:

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

和提供者方法,如:

/** 
 * 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);
}

但等等还有更多!假设您想要一个单一的屏幕/蓝图定义,为不同的形状因子制造不同的模块。我们已经开始使用注释方案来简化这种事情。而不是让我们的屏幕类全部实现 BluePrint,我们已经开始使用一些注释来声明它们的接口类。在这个世界中,屏幕可以选择性地选择用于平板电脑或移动设备的模块。

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

魔术吧?这就是幕后的情况。首先,a ModuleFactory 是一些静态类,可以访问屏幕 和资源 并吐出一把匕首模块。

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

我们的三重奏 ResponsiveModuleFactory 子类看起来像这样。 (记住怎么做 ShowTabletUi.java 将资源ID定义为常量?这就是为什么。)

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

为了实现这一切,我们有一个ScreenScoper类(如下所示)。在Mortar示例代码中,您将使ScreenConductor使用其中一个来创建和销毁范围。迟早(很快我希望)Mortar和/或其样品将更新以包含这些东西。

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

16
2017-07-31 17:53



我昨天发布的ScreenScoper有一个缓存错误。我刚刚修复了它。 - rjrjr
这非常有用!什么是MortarScreen类?是否所有Screen都延伸出来的是一个空类?与蓝图有何不同? - Nelson Osacky
@NelsonOsacky这是一个定义String getName()方法的接口。 - rjrjr


答案:


我们仍然在为此做一个规范的答案,但基本的想法是你让资源系统改变你在哪种情况下显示的观点。因此,您的活动会将其内容视图设置为,例如, R.layout.root_view。该布局的平板电脑版本(我们把它放入 res/layout-sw600dp)可以绑定到不同的视图,这可能会注入不同的演示者,等等。

对于需要做出运行时决策的情况,请在中定义一个布尔资源 values/bools .xml

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

values-sw600dp/bools.xml

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

通过匕首将其暴露给应用程序的其余部分。使用此绑定注释:

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

和提供者方法,如:

/** 
 * 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);
}

但等等还有更多!假设您想要一个单一的屏幕/蓝图定义,为不同的形状因子制造不同的模块。我们已经开始使用注释方案来简化这种事情。而不是让我们的屏幕类全部实现 BluePrint,我们已经开始使用一些注释来声明它们的接口类。在这个世界中,屏幕可以选择性地选择用于平板电脑或移动设备的模块。

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

魔术吧?这就是幕后的情况。首先,a ModuleFactory 是一些静态类,可以访问屏幕 和资源 并吐出一把匕首模块。

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

我们的三重奏 ResponsiveModuleFactory 子类看起来像这样。 (记住怎么做 ShowTabletUi.java 将资源ID定义为常量?这就是为什么。)

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

为了实现这一切,我们有一个ScreenScoper类(如下所示)。在Mortar示例代码中,您将使ScreenConductor使用其中一个来创建和销毁范围。迟早(很快我希望)Mortar和/或其样品将更新以包含这些东西。

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

16
2017-07-31 17:53



我昨天发布的ScreenScoper有一个缓存错误。我刚刚修复了它。 - rjrjr
这非常有用!什么是MortarScreen类?是否所有Screen都延伸出来的是一个空类?与蓝图有何不同? - Nelson Osacky
@NelsonOsacky这是一个定义String getName()方法的接口。 - rjrjr