Injeção de Dependência Pura - Como implementá-lo

5

Estou fazendo um projeto Android para um curso on-line. Eu gostaria de usar o DI nesse projeto, então comecei a usar o dagger2, mas agora eu comecei a ter problemas típicos de newbie que estão me impedindo.

Como o prazo final do projeto está chegando, eu decido abandonar o punhal2 para este projeto, mas eu ainda gosto de usar algum tipo de DI.

Então eu vim a este artigo:

Injeção de Dependência Pura ou Injeção de Dependência de Homem Pobre

Mas eu não entendi exatamente como implementar isso no Android. Eu poderia começar a evitar a criação de instâncias de meus objetos no construtor ou dentro da classe dependente, mas em algum lugar ele tem que ser instanciado.

Estou procurando uma ideia ou uma estratégia de implementação para esse "Pure DI" no Android. Se possível, um exemplo bem implementado.

    
por alexpfx 26.07.2017 / 04:38
fonte

2 respostas

6

Você provavelmente já terminou seu curso agora, mas no caso de você ainda estar pesquisando, ou outra pessoa: rolando seu próprio DI é realmente muito simples no Android, uma vez que você sabe como fazer isso.

Criando o gráfico de dependência

Eu geralmente começo primeiro com uma classe de aplicativo personalizada (não se esqueça de registrá-lo no manifesto android), essa classe vai viver em todo o ciclo de vida do seu aplicativo android e é onde o seu código pode acessar suas dependências. Algo parecido com isto:

public class CustomApp extends Application {

  private static ObjectGraph objectGraph;


  @Override
  public void onCreate() {
    super.onCreate();

    objectGraph = new ObjectGraph(this);
  }


  // This is where your code accesses its dependencies
  public static <T> T get(Class<T> s) {
    Affirm.notNull(objectGraph);
    return objectGraph.get(s);
  }


  // This is how you inject mock dependencies when running tests
  public <T> void injectMockObject(Class<T> clazz, T object) {
    objectGraph.putMock(clazz, object);
  }

}

(Affirm.notNull () apenas explode se algo é nulo, você não precisa usá-lo). As dependências reais estão todas na classe ObjectGraph que basicamente se parece com isto:

class ObjectGraph {

  private final Map<Class<?>, Object> dependencies = new HashMap<>();

  public ObjectGraph(Application application) {

    // Step 1.  create dependency graph
    AndroidLogger logger = new AndroidLogger();
    Wallet wallet = new Wallet(logger);

    //... this list can get very long


    // Step 2. add models to a dependencies map if you will need them later
    dependencies.put(Wallet.class, wallet);

  }

  <T> T get(Class<T> model) {

    Affirm.notNull(model);
    T t = model.cast(dependencies.get(model));
    Affirm.notNull(t);

    return t;
  }

  <T> void putMock(Class<T> clazz, T object) {

    Affirm.notNull(clazz);
    Affirm.notNull(object);

    dependencies.put(clazz, object);
  }

}

Uso

Agora, onde quer que você esteja no seu aplicativo (em uma atividade, por exemplo), você pode injetar suas dependências assim:

private Wallet wallet;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    wallet = CustomApp.get(Wallet.class);

    //...
}

Você pode usar o Dagger de algumas maneiras diferentes, mas o equivalente mais próximo do Dagger2 seria algo assim:

AppComponent appComponent = CustomApp.getAppComponent();
Wallet wallet  = appComponent.getWallet();

Dependências com escopo

O que você estaria injetando aqui seria uma classe que tenha o escopo de nível application . Se você quer apenas um objeto escopo local que só existe enquanto você mantiver uma referência a ele em sua visão ou atividade, você faz exatamente a mesma coisa, mas o que você injeta é uma fábrica classe:

No ObjectGraph:

WalletFactory walletFactory = new WalletFactory(logger);

No seu fragmento, por exemplo:

Wallet wallet = CustomApp.get(WalletFactory.class).getNewWallet();

Teste

Tudo isso é feito para que você possa testar seu código da camada de visualização com facilidade. Por exemplo, se você deseja executar um teste de café expresso, crie o aplicativo, mas antes de mostrar a atividade, substitua a instância da carteira por uma simulação:

CustomApp.injectMockObject(Wallet.class, mockedWalletWith100Dollars);

Esta é a instância da carteira que será capturada pelo restante do seu código durante o teste.

De certa forma, este estilo de DI não é tão flexível quanto o Dagger2, mas eu acho que é muito mais claro - apenas não é necessário fazer o DI complicado, é na verdade uma coisa bastante básica. Esse estilo também resulta frequentemente em menos placa de caldeira do que usando uma estrutura DI (uma vez que você inclui classes de componentes e módulos).

Exemplos completos

Eu escrevi algo semelhante para 5 exemplos de aplicativos como parte de uma estrutura que publiquei (querendo manter as amostras tão acessíveis quanto possível - nem todo mundo gosta de Dagger). Você pode ver os exemplos completos aqui: link

    
por 13.12.2017 / 13:45
fonte
2

Para classes Java simples no Android, a DI pura é implementada como de costume com os parâmetros do construtor. Por exemplo:

interface UserService extends Parcelable {
    User getUser(String name);
}

class DbUserService implements UserService {
    private final DbHelper db;

    public RestUserService(DbHelper db) {
        this.db = db;
    }

    User getUser(String name) {
        // use db to get user by name
    }
}

A complexidade de fazer DI puro no Android vem com a integração do framework. Como fazemos DI com Activity s quando as atividades são construídas pelo Android para nós? Não é tão simples, mas a correlação mais próxima dos parâmetros do construtor é Bundle s que você pode anexar em Intent s. Vamos usar um exemplo para demonstrar como isso funcionaria:

Digamos que eu tenha uma atividade UserActivity que busca um usuário de um UserService e o exibe em uma visualização. Eu quero injetar o UserService em UserActivity para torná-lo mais testável (junto com todos os outros benefícios da DI).

class UserActivity extends Activity {
    private Optional<UserService> userService = Optional.empty();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // get UserService from bundle
        userService = Optional.of(getIntent().getExtras().getParcelable<UserService>("userService"));
        setContentView(R.layout.activity_user);
    }

    @Override
    protected void onResume() {
        super.onResume();
        userService.map(us -> us.getUser("Bill"));
    }
}

Como Intent implementa Parcelable , podemos construir nosso gráfico de objeto na raiz da composição assim:

UserService userService = new DbUserService(...);
Intent userActivityIntent = new Intent(this, UserActivity.class);
userActivity.putExtra("userService", userService);
Intent mainIntent = new Intent(this, MainActivity.class);
mainIntent.putExtra("userActivityIntent", userActivityIntent);
startActivity(mainIntent);

As limitações desta abordagem é que a passagem de argumentos depende muito de Parcelable . Todas as suas dependências devem ser executáveis. Muitas classes do Android não implementam (e não devem) Parcelable , portanto, você deve projetar seu aplicativo de maneira que não seja necessário transmiti-lo.

    
por 26.07.2017 / 05:28
fonte