javagwtclientbundle

Is it an ok practice to have a member ClientBundle in a containing ClientBundle?


In my app, I have MyAppResources, which will mainly contain custom styles for the app. I am thinking about what is a good way to go about applying custom styles to standard widgets, such as a CellTable, along with custom styles on the layout and custom widgets?

My question: Since MyAppResources is a singleton (it doesn't have to be, as mentioned in other posts), but CellTableResources isn't, and CellTableResources is a member of this instance that is an interface also extending ClientBundle, will a proxy 'CellTableResources' be created on every MyAppResources.INSTANCE.cellTableResources().foo()?

If so, could I create a MyAppResources.CELLTABLE_RESOURCE_INSTANCE to get around this? Or would the creation of the proxy be negligible, even if there are plentiful calls to MyAppResources.INSTANCE.cellTableResources().#?

Secondly, more of a discussion question: what is best practice in regards to using multiple ClientBundles in this case? Should I instead use CellTableResources seperately (remove it from MyAppResources), using GWT.create(CellTableResources.class); in a widget that needs it (or using a singleton like I have for MyAppResources)?

MyAppResources:

public interface MyAppResources extends ClientBundle {
    public static final MyAppResources INSTANCE =  GWT.create(MyAppResources.class);

    @Source("MyAppStyles.css")
    public MyAppCssResource css();

    public CellTableResources cellTableResources();

}

CellTableResources:

public interface CellTableResources extends CellTable.Resources {

    interface CellTableStyle extends CellTable.Style {
    }

    @Override
    @Source({ CellTable.Style.DEFAULT_CSS, "CellTableStyles.css" })
    CellTableStyle cellTableStyle();

    @Source("green_light.png")
    ImageResource getGreenLight();

    //...
}

Thank you for reading.


Solution

  • Multi-part question, so I'm going to try to hit this in several parts:

    What is the cost of GWT.create()?

    Most of the GWT class is 'magic', things that you cannot wrote for yourself in other ways, as they call on the compiler to fill in specific details for you. These are often different when running in dev mode vs compiled to JS.

    In the case of GWT.create, it turns out that this is compiled out to new - it is used just to create new instances. So what is the cost of a new instance versus a singleton? This depends entirely on the object being created. If there are no fields in the object, then the cost is essentially free - in fact, the compiler may choose to actually remove the constructor call, and rewrite all later methods as static anyway!

    This is what happens in most cases - GWT.create should be considered to be very cheap, unless you are doing something silly like calling it within a loop that is run many times.

    What happens when I list a ClientBundle method inside another ClientBundle?

    Well, what happens when you list anything inside a ClientBundle?

    Anything that can be listed in a ClientBundle must be annotated with @ResourceGeneratorType, indicating how to generate that type. For example, here is ImageResource:

    /**
     * Provides access to image resources at runtime.
     */
    @DefaultExtensions(value = {".png", ".jpg", ".gif", ".bmp"})
    @ResourceGeneratorType(ImageResourceGenerator.class)
    public interface ImageResource extends ResourcePrototype {
    //...
    

    It calls on ImageResourceGenerator to create images as needed. Any class described in that annotation must implement com.google.gwt.resources.ext.ResourceGenerator, which describes how to get ready to work, how to create necessary fields, how to initialize them, and how to finish up.

    So what does this look like for ClientBundle itself? Check out com.google.gwt.resources.rg.BundleResourceGenerator - it is a very simple class that just calls GWT.create() on the type of the method given. So, predictable, this means that those 'child' ClientBundles are created via GWT.create, more or less the same as you might otherwise do.

    Okay, what does that mean in this specific case?

    It turns out that ClientBundles instances don't have fields where they track newly created objects from, but instead have static members that they use instead - effectively singletons. This means that once you have called a method once, the instance it returns will be the same instance created as the next time you call it. Two different ClientBundles with the same contents will of course then keep two different copies of the objects, but it doesn't matter how many times you create a ClientBundle - its internals will always be the same.

    Anything else?

    Yep! Remember that you are dealing with interfaces here, not classes, so you can actually extend more than once at once!

    public interface MyAppResources extends 
           ClientBundle, 
           CellTable.Resources, 
           CellTree.Resources {//etc
      //...
    

    Now, if two interfaces describe the same methods you may have problems, but if not, this can provide an advantage when generated sprited images. Each individual ClientBundle will draw on its own pool of images when preparing them for use - if you have a ClientBundle within a ClientBundle, they won't work together to sprite images into bigger pieces. To get that, you need to make just one ClientBundle type. This may not matter in your particular case, but I figured it was also worth mentioning.