View Model Instantiation

The FactoryService service covers all use-cases for programmatic view model creation and takes care, that all injection points are resolved for the resulting view model instance.

Simple Case (using the public no-arg constructor)
CustomerViewModel viewModel = factoryService.viewModel(CustomerViewModel.class);
Advanced Case (using a custom constructor)
CustomerViewModel viewModel = factoryService.viewModel(new CustomerViewModel("Joe", "Bloggs"));

TODO outdated docs (likely to just be removed) …​

TODO - v2 - don’t know if this is still valid or not. If it is, then we should probably remove FactoryService#create(…​) for view models completely.

With view models, some care must be taken in how they are instantiated. Specifically, it’s important that the framework doesn’t "know" about the view model until its state is "sufficiently" populated to distinguish from other view models.

In practical terms, this means that view models should be instantiated using a constructor, and then injecting services (if required) using the ServiceRegistry service:

CustomerViewModel viewModel = new CustomerViewModel("Joe", "Bloggs");
serviceInjector.injectServicesInto(viewModel);

What will most likely fail is to use the FactoryService:

// DON'T DO THIS WITH VIEW MODELS
CustomerViewModel viewModel = factoryService.instantiate(CustomerViewModel.class);

viewModel.setFirstName("Joe");
viewModel.setLastName("Bloggs");
serviceInjector.injectServicesInto(viewModel);

That’s because the internal "OID" identifier that the framework creates to handle this view model will be incomplete. Although the framework can handle changes to the OID (when the corresponding view model’s state changes) once created, this isn’t the case during initial instantiation process.

Example

To explain further, here’s an implementation using FactoryService that fails:

@XmlRootElement(name = "yearSummary")
@XmlType( propOrder = { /* ... */ } )
@XmlAccessorType(XmlAccessType.FIELD)
public class YearSummary {                                                  (1)
    ...
    @XmlTransient
    @CollectionLayout(defaultView = "table")
    public List<OfficeOptionViewModel> getAmountsPerOffice() {
        List<OfficeOptionViewModel> amountsPerOffice = new ArrayList<>();

        OfficeOptionViewModel office1 =                                     (2)
            factoryService.viewModel(OfficeOptionViewModel.class);
        office1.setOffice("Amsterdam");                                     (3)
        office1.setAmount(200);
        amountsPerOffice.add(office1);

        OfficeOptionViewModel office2 =                                     (2)
            factoryService.viewModel(OfficeOptionViewModel.class);
        office2.setOffice("London");                                        (3)
        office2.setAmount(100);
        amountsPerOffice.add(office2);

        return amountsPerOffice;
    }
}
1 Parent view model
2 Using FactoryService, incorrectly.
3 Hard-coded just for demo purposes

This collection, is, confusing, rendered as:

view model fail

Even though the amountsPerOffice collection of view models is correctly populated, the framework/viewer maps these to their corresponding OIDs before they are rendered. Because the "Amsterdam" pojo and "London" pojo each mapped to the same OID, when fetching out the results the viewer obtains the London pojo both times.

The following implementation, on the other hand, succeeds:

@XmlRootElement(name = "yearSummary")
@XmlType( propOrder = { /* ... */ } )
@XmlAccessorType(XmlAccessType.FIELD)
public class YearSummary {
    ...
    @XmlTransient
    @CollectionLayout(defaultView = "table")
    public List<OfficeOptionViewModel> getAmountsPerOffice() {
        List<OfficeOptionViewModel> amountsPerOffice = new ArrayList<>();

		OfficeOptionViewModel office1 = new OfficeOptionViewModel("Amsterdam", 200);    (1)
		serviceInjector.injectServicesInto(office1);
		amountsPerOffice.add(office1);

		OfficeOptionViewModel office2 = new OfficeOptionViewModel("London", 100);       (1)
		serviceInjector.injectServicesInto(office2);
		amountsPerOffice.add(office2);

        return amountsPerOffice;
    }
}
1 Just instantiate with constructor. The framework "sees" the domain object when services are injected into it.

As can be seen, this renders just fine:

view model success

To complicate matters a little, note though that following "incorrect" implementation using FactoryService does also work correctly:

@XmlRootElement(name = "yearSummary")
@XmlType( propOrder = { ..., "amountsPerOffice" } )                     (1)
@XmlAccessorType(XmlAccessType.FIELD)
public class YearSummary {
	...

    void init() {
        amountsPerOffice = calculateAmountsPerOffice();
    }

    @XmlElementWrapper
    @XmlElement(name = "officeOption")
    @CollectionLayout(defaultView = "table")
    @Getter @Setter
    private List<OfficeOptionViewModel> amountsPerOffice = Lists.newArrayList();

	@XmlTransient
    @CollectionLayout(defaultView = "table")
    public List<OfficeOptionViewModel> calculateAmountsPerOffice() {
        List<OfficeOptionViewModel> amountsPerOffice = new ArrayList<>();

		OfficeOptionViewModel office1 = factoryService.viewModel(OfficeOptionViewModel.class);
		office1.setOffice("Amsterdam");
		office1.setAmount(200);

		amountsPerOffice.add(office1);

		OfficeOptionViewModel office2 = factoryService.viewModel(OfficeOptionViewModel.class);
		office2.setOffice("London");
		office2.setAmount(100);

		amountsPerOffice.add(office2);

        return amountsPerOffice;
    }
}
1 "amountsPerOffice" is part of the state of the parent view model

In this case the amountsPerOffice collection is part of the state of the parent view model and so in this particular case everything works with either FactoryService#create(…​) or using ServiceRegistry.