square/flow

Parent service was bound before child in MultiKey.

novachevskyi opened this issue · 2 comments

I faced an issue when I was trying to setup MultiKey with child, when child's component should be inherited from MultiKey's component. In other words, child services are always created before parent MultiKey service.

Let me show an example. MultiKey declaration:

class ParentMultiKey implements MultiKey {
    @Override public List<Object> getKeys() {
        return Collections.singletonList(new ChildKey());
    }
}

ParentMultiKey's component:

@Component
interface ParentComponent {}

Child key declaration:

class ChildKey {}

ChildKey's component:

@Component(dependencies = ParentComponent.class)
interface ChildComponent {}

ServicesFactory implementation:

class SampleServicesFactory extends ServicesFactory {
    private static final String DAGGER_COMPONENT = "DAGGER_COMPONENT";
    private static Map<Class, Object> cachedComponents = new HashMap<>();

    @Override
    public void bindServices(@NonNull Services.Binder services) {
        Object key = services.getKey();

        if (key instanceof ParentMultiKey) {
            Object component = DaggerParentComponent.builder().build();

            cachedComponents.put(ParentComponent.class, component);
            services.bind(DAGGER_COMPONENT, component);
        } else if (key instanceof ChildKey) {
            Object component = DaggerChildComponent.builder()
                    .parentComponent((ParentComponent) cachedComponents.get(ParentComponent.class))
                    .build();

            cachedComponents.put(ChildComponent.class, component);
            services.bind(DAGGER_COMPONENT, component);
        }
    }
}

As you can see, my ChildKeyComponent is inherited from ParentMultiKeyComponent. At the moment of services setup, ChildKey's services would be created before ParentMultiKey's services.

The possible fix is to place KeyManager::ensureNode call before foreach within KeyManager::setUp method:

void setUp(Object key) {
    Services parent = managedServices.get(ROOT_KEY).services;
    if (key instanceof MultiKey) {
      ensureNode(parent, key).uses++;
      for (Object part : ((MultiKey) key).getKeys()) {
        setUp(part);
      }
    } else if (key instanceof TreeKey) {
      TreeKey treeKey = (TreeKey) key;
      final Object parentKey = treeKey.getParentKey();
      setUp(parentKey);
      parent = managedServices.get(parentKey).services;
      ensureNode(parent, key).uses++;
    } else {
      ensureNode(parent, key).uses++;
    }
  }

Please let me know if I'm missing something here or if some other solution should be applied to fix this issue. I will appreciate for your help.

This is interesting. I hadn't actually considered that a multikey would have any relationship to its constituent keys. It seems that what you should have instead is constituent keys implemented as TreeKeys with some shared ancestor.

It's probably a mistake for a MultiKey to be a key itself, especially in light of this.

Closing, #207 is the way to go here.