»
November 02, 2009
»

GWT.runAsync with GIN

GWT-2.0 has great new feature called Code Splitting what allows to split module in smaller compiled parts and request parts only when (if) they’re needed. But I’m using GIN to wire all internal application object graph (models, presenters, views, service classes) and of corse if it’s left as is (one Ginjector) no code is split.

So I came up with this simple “design pattern” to continue using GIN modules while taking advantage of code splitting:

  • Split monolithic GIN module in multiple modules
  • For each GIN/runAsync module create Loader interface and implementation with void load(LoaderCallback cb) method

Loader:

  • on first load creates GIN module
  • possibly injects module stylesheets
  • stores GIN injector instance in private static field
  • connects parent and new GIN modules together
  • calls LoaderCallback method

Example

In this example (live version is deployed on AppEngine) I have one “base” StoriesModule:

public class StoriesModule extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(EventBus.class).to(EventBusImpl.class);
    bind(EventBusListener.class).to(LoggingEventBusListener.class);
    
    bind(AdminLoader.class).to(AdminLoaderImpl.class);
  }

}

and AdminModule:

public class AdminModule extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(AdminPresenter.class).to(AdminPresenterImpl.class);
    bind(AdminView.class).to(AdminViewImpl.class);
  }

}

Pretty basic stuff.

But AdminModule needs EventBus instance from StoriesModule. To connect both modules add 2 new interfaces:

public interface SharedServices {

  EventBus getEventBus();

}
public interface SharedServicesAware {

  void setSharedServices(SharedServices services);

}

In “base” module add SharedServices provider:

@Provides
@Singleton
public SharedServices sharedServices(final EventBus eventBus) {
  return new SharedServices() {
    public EventBus getEventBus() {
      return eventBus;
    }
  };
}

In “child” module add class what implements both SharedServicesAware and Provider<SharedServices>:

@Singleton
public static class SharedServicesAdapter implements Provider<SharedServices>, SharedServicesAware {

  private SharedServices services;

  public void setSharedServices(SharedServices services) {
    this.services = services;
  }

  public SharedServices get() {
    return services;
  }

}

And now EventBus in “child” module can be bound using SharedServices like this:

@Provides
public EventBus eventBus(SharedServices sharedServices) {
  return sharedServices.getEventBus();
}

The last thing — Loader.

Child module loader should contain only one method load:

public interface AdminLoader {

  public interface AdminLoaderCallback {
    void onLoaded(AdminPresenter admin);
  }

  void load(AdminLoaderCallback callback);

}

Implementation:

@Singleton
public class AdminLoaderImpl implements AdminLoader {

  private static AdminInjector injector;
  private final SharedServices sharedServices;

  @Inject
  public AdminLoaderImpl(SharedServices sharedServices) {
    this.sharedServices = sharedServices;
  }

  public void load(final AdminLoaderCallback callback) {
    GWT.runAsync(AdminLoader.class, new RunAsyncCallback() {
      public void onSuccess() {
        if (injector == null) {
          onFirstLoad();
        }

        callback.onLoaded(injector.presenter());
      }

      private void onFirstLoad() {
        // Inject just loaded stylesheets
        StyleInjector.inject(AdminClientBundle.I.css().getText());

        // create Injector and connect both "worlds"
        injector = GWT.create(AdminInjector.class);
        injector.sharedServicesAware().setSharedServices(sharedServices);
      }

      public void onFailure(Throwable reason) {
        Window.alert("Failed to load admin presenter");
      }
    });
  }

}

Note I’m setting AdminInjector as static. It’s because AdminLoaderImpl can be bound in more than one Module. This ensures that only one AdminInjector instance is present in application.

SOYC

Initialy downloaded code contains only AdminLoader:

Initially downloaded code

All other code comes after the split point:

Admin module

Download example

 
Internet Explorer 6
Are you serious?