Single presenter that can switch view in child module

59 views
Skip to first unread message

the.wizard

unread,
Jan 12, 2015, 3:05:07 AM1/12/15
to mv...@googlegroups.com
Hi everyone, 
I have a requirement for my apps whenever user access from mobile, I must switch the view to mobile version while still using the same presenter for the desktop version. Basically, my apps have one main module which contains of : 
/*----- IMainEventBus.java ------- */
@Events(startPresenter = MainPresenter.class, historyOnStart = true, ginModules = Mvp4gGinModule.class)
@Debug(logLevel = LogLevel.SIMPLE, logger = CustomLogger.class)
@ChildModules({
@ChildModule(moduleClass = ICustomModule.class, async = true, autoDisplay = false)
})
@PlaceService(CustomPlaceService.class)
public interface IMainEventBus extends EventBusWithLookup {
@Event(forwardToModules = ICustomModule.class, historyConverter = MainHistoryConverter.class, name = "custom", navigationEvent = true)
void goToCustom();
@Event(handlers = MainPresenter.class)
void changeBody(IsWidget body);
@LoadChildModuleError
@Event(handlers = MainPresenter.class)
void errorOnLoad(Throwable reason);
@BeforeLoadChildModule
@Event(handlers = MainPresenter.class)
void beforeLoad();
@AfterLoadChildModule
@Event(handlers = MainPresenter.class)
void afterLoad();
@InitHistory
@Event(handlers = MainPresenter.class)
void start();
}

/*--------- IMainView.java -------------*/
public interface IMainView extends ReverseViewInterface<IMainView.IMainPresenter> {
public interface IMainPresenter {
}
void setBody(IsWidget body);
public void maskLoading(boolean isMask);
public void maskError(boolean isMask, String errorMessage);
}

/*-------- MainPresenter.java ---------*/
@Presenter(view = MainView.class)
public class MainPresenter extends BasePresenter<IMainView, IMainEventBus> implements IMainPresenter {
public void onStart() {
eventBus.setApplicationHistoryStored(true);
}
public void onChangeBody(IsWidget body) {
view.setBody(body);
}
public void onErrorOnLoad(Throwable reason) {
view.maskError(true, "Error Message : " + reason.getMessage());
}
public void onBeforeLoad() {
view.maskLoading(true);
}
public void onAfterLoad() {
view.maskLoading(false);
}
}

/*---------- MainView.java -------------*/
public class MainView extends SimplePanel implements IMainView {
private IMainPresenter presenter;
private final PopupPanel popup = new PopupPanel(false, true); // Create a modal dialog box that will not auto-hide
private final PopupPanel popupError = new PopupPanel(false, true); // Create a modal dialog box that will not auto-hide
private final Label errorLabel = new Label();

public MainView() {
super();

popup.add(new Label("Please wait"));
popup.setGlassEnabled(true); // Enable the glass panel
popupError.add(errorLabel);
popupError.setGlassEnabled(true); // Enable the glass panel
}
public void setBody(IsWidget body) {
setWidget(body);
}
@Override
public void setPresenter(IMainPresenter _presenter) {
this.presenter = _presenter;
}
@Override
public IMainPresenter getPresenter() {
return this.presenter;
}
public void maskLoading(boolean isMask) {
if (isMask) 
popup.center(); // Center the popup and make it visible
else 
popup.hide();
}
public void maskError(boolean isMask, String errorMessage) {
if (isMask) {
errorLabel.setText(errorMessage);
popupError.center();
}
else
popupError.hide();
}
}

/*------- Mvp4gGinModule.java -------*/
public class Mvp4gGinModule extends AbstractGinModule {
@Override
protected void configure() {
}
}
/*------- end of main module --------*/

Then I have one child module consists of these: 
/*------- ICustomModule.java ------------*/
@HistoryName("custom")
public interface ICustomModule extends Mvp4gModule {
}

/*----- ICustomEventBus.java -------*/
@Events(startPresenter = CustomPresenter.class, module = ICustomModule.class, ginModuleProperties = "ginModule")
public interface ICustomEventBus extends EventBus {
@Event(forwardToParent = true)
void changeBody(IsWidget body);
@Event(handlers = CustomPresenter.class, navigationEvent = true)
void goToCustom();
}

/*----- ICustomView.java --------*/
public interface ICustomView extends IsWidget, LazyView, ReverseViewInterface<ICustomView.ICustomPresenter> {
public interface ICustomPresenter {
public Integer getSecretNumber();
}
}

/*---- CustomPresenter.java -----*/
@Presenter(view = CustomView.class)
public class CustomPresenter extends LazyPresenter<ICustomView, ICustomEventBus> implements ICustomPresenter {
private Integer secretNumber = 88;
@Override
public void setView(ICustomView view) {
GWT.log("setView");
String ua = Navigator.getUserAgent().toLowerCase();
if (ua.indexOf("iphone") != -1) {
// iPhone device
        } else if (ua.indexOf("android") != -1) { 
           // android device
           this.view = new CustomMobileView();      
               } else {
           // not mobile device
           this.view = new CustomView();
        }
this.view.setPresenter(this);
}
@Override
public void bindView() {
GWT.log("bindView");
}
public void onGoToCustom() {
eventBus.changeBody(view);
}
@Override
public Integer getSecretNumber() {
return secretNumber;
}
}

/*----- CustomView.java --------*/
public class CustomView extends Composite implements ICustomView {
private ICustomPresenter presenter;
@Override
public void createView() {
Button btn = new Button("Desktop Web Button");
btn.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
Window.alert("Greetings from desktop web! Here is our secret number: "+presenter.getSecretNumber());
}
});
initWidget(btn);
}
public void setPresenter(ICustomPresenter presenter) {
this.presenter = presenter;
}
@Override
public ICustomPresenter getPresenter() {
return this.presenter;
}
}

/*-------- CustomMobileView.java ------*/
public class CustomMobileView extends Composite implements ICustomView {
private ICustomPresenter presenter;
@Override
public void createView() {
AnimationHelper animationHelper = new AnimationHelper();
RootPanel.get().add(animationHelper);
RootFlexPanel rootFlexPanel = new RootFlexPanel();
Button button = new Button("Hello mgwt");
button.addTapHandler(new TapHandler() {
@Override
public void onTap(TapEvent event) {
AlertDialog dialog = new AlertDialog("Information", "Hello there! Here is our secret number: "+presenter.getSecretNumber());
dialog.show();
}
});
rootFlexPanel.add(button);
animationHelper.goTo(rootFlexPanel, Animations.SLIDE);
}
public void setPresenter(ICustomPresenter presenter) {
this.presenter = presenter;
}
@Override
public ICustomPresenter getPresenter() {
return this.presenter;
}
}
/*------- end of custom module --------*/

Then here are my questions:
  1. What is the pros and cons of my technique? From what I learn at mvp4g group, this technique was called mvp4g technique.
  2. Is there any better solution beside this technique, because somehow I just felt it's a little bit clumsy. I read about the GIN technique from here: https://code.google.com/p/mvp4g/wiki/GinIntegration#Setting_GIN_module_for_multiple_devices. I have tried it but still failed because the child module extends Mvp4gModule and not AbstractGinModule. I don't know how to do it for the child module, and the example Mvp4gModules just show it for parent module. Could someone help me implement this gin technique?
  3. It is a good thing to use single presenter with switchable view? I choose this technique because I want to reuse the business logic inside my presenter instead of creating two presenter, is this the right way? Any thought or share will be appreciated.
  4. What I need is using single presenter and the view was depend on user browser, if it is mobile web browser, the mobile view was selected, if it was desktop browser, the desktop view was selected. Am I on the right track to achieve my goal?
Thanks & Regards.
Message has been deleted

Frank Hossfeld

unread,
Jan 12, 2015, 4:08:38 PM1/12/15
to mv...@googlegroups.com

First, before I answer your questions: 

I don't know this so called mvp4g technique. Please can you provide some links where do I will find it (It does not mean, that this technique is incorrect, it means I never read about it.)

I solved your problem in a different way. Maybe you can adopt it. Especially because your application design will be easy to change to.

First you have to clone this Git repository:

https://github.com/gwt4e/gwt4e

Now run install from the pom to create a jar or just copy the file you find under:

https://github.com/gwt4e/gwt4e/blob/master/src/main/resources/org/gwt4e/envirement/DeviceDetector.gwt.xml

into your project. (This is a early beta from our new framework. It will provide a lot of solutions that can be very useful when writing GWT applications) Feel free to extend the detection code.

Add this line to your module descriptor:

<inherits name='org.gwt4e.evirement.DeviceDetector'/>

Add something like this for every view of your application to your module descriptor:

for desktop (as default):

<replace-with class="x.x.x.client.CustomView">
    <when-type-is class="x.x.x.client.ICustomView"/>
</replace-with>


for mobile:

<replace-with class="x.x.x.client.CustomMobileView">
    <when-type-is class="x.x.x.client.ICustomView"/>
    <when-property-is name="deviceDetector" value="mobile"/>
</replace-with>


for tablet:

<replace-with class="x.x.x.client.CustomMobileView">
    <when-type-is class="x.x.x.client.ICustomView"/>
    <when-property-is name="deviceDetector" value="tablet"/>
</replace-with>

(Now you have assigned your view to your devices)

Next remove the setView-code from your presenters. It isn't needed anymore. The views will be created via deferred binding during the process of compilation.

Compared to your current solution you have the advantage, that every device has it's own permutation.

Hope that helps.

Frank

the.wizard

unread,
Jan 13, 2015, 7:33:22 AM1/13/15
to mv...@googlegroups.com
Hi Frank,
I have tried your solution, and it's doesn't work out when I tried to access the apps from my tablet device, it still give me the desktop view. 
Here is my changes: 
/****** ChameleonLab.gwt.xml *********/
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='chameleonlab'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User' />

<!-- Other module inherits -->
<inherits name='com.google.gwt.precompress.Precompress' />
<inherits name='com.mvp4g.Mvp4gModule' />
<inherits name='com.googlecode.mgwt.MGWT' />
<inherits name='app.chameleon.sample.DeviceDetector' />
<replace-with class="app.chameleon.sample.client.module.custom.view.CustomView">
    <when-type-is class="app.chameleon.sample.client.module.custom.presenter.view.interfaces.ICustomView"/>
</replace-with>
<replace-with class="app.chameleon.sample.client.module.custom.view.CustomMobileView">
    <when-type-is class="app.chameleon.sample.client.module.custom.presenter.view.interfaces.ICustomView"/>
    <when-property-is name="deviceDetector" value="mobile"/>
</replace-with>
<replace-with class="app.chameleon.sample.client.module.custom.view.CustomMobileView">
    <when-type-is class="app.chameleon.sample.client.module.custom.presenter.view.interfaces.ICustomView"/>
    <when-property-is name="deviceDetector" value="tablet"/>
</replace-with>
<!-- Specify the app entry point class. -->
<entry-point class='app.chameleon.sample.client.ChameleonLab' />

</module>

Here is my project structure, I copy your DeviceDetector.gwt.xml and move it to my local project : 

I have remove the setView code from my presenter. Here is what printed in console when I accessed from my desktop browser: 
GET /recompile/chameleonlab
   Job app.chameleon.sample.ChameleonLab_1_2
      starting job: app.chameleon.sample.ChameleonLab_1_2
      binding: deviceDetector=desktop
      binding: mgwt.density=mid
      binding: mgwt.formfactor=desktop
      binding: user.agent=safari
      Compiling module app.chameleon.sample.ChameleonLab
         Unification traversed 369 fields and methods and 145 types. 0 are considered part of the current module and 0 had all of their fields and methods traversed.
         Compiling 1 permutation
            Compiling permutation 0...
            Linking per-type JS with 0 new types.
               prelink JS size = 212
               prelink sourcemap = 212 bytes and 5 lines
               postlink JS size = 2342256
               postlink sourcemap = 2342256 bytes and 68072 lines
            Source Maps Enabled
         Compile of permutations succeeded
         Compilation succeeded -- 1,116s
      Linking into D:\Users\Administrator\AppData\Local\Temp\gwt-codeserver-642726097902823196.tmp\app.chameleon.sample.ChameleonLab\compile-5\war\chameleonlab; Writing extras to D:\Users\Administrator\AppData\Local\Temp\gwt-codeserver-642726097902823196.tmp\app.chameleon.sample.ChameleonLab\compile-5\extras\chameleonlab
         Link succeeded
         Linking succeeded -- 0,315s
      1,513s total -- Compile completed

Here is the console content when I accessed from my tablet device: 
GET /recompile/chameleonlab
   Job app.chameleon.sample.ChameleonLab_1_3
      starting job: app.chameleon.sample.ChameleonLab_1_3
      binding: deviceDetector=tablet
      binding: mgwt.density=mid
      binding: mgwt.formfactor=tablet
      binding: user.agent=safari
      Compiling module app.chameleon.sample.ChameleonLab
         Unification traversed 369 fields and methods and 145 types. 0 are considered part of the current module and 0 had all of their fields and methods traversed.
         Compiling 1 permutation
            Compiling permutation 0...
            Linking per-type JS with 0 new types.
               prelink JS size = 212
               prelink sourcemap = 212 bytes and 5 lines
               postlink JS size = 2342256
               postlink sourcemap = 2342256 bytes and 68072 lines
            Source Maps Enabled
         Compile of permutations succeeded
         Compilation succeeded -- 0,783s
      Linking into D:\Users\Administrator\AppData\Local\Temp\gwt-codeserver-642726097902823196.tmp\app.chameleon.sample.ChameleonLab\compile-6\war\chameleonlab; Writing extras to D:\Users\Administrator\AppData\Local\Temp\gwt-codeserver-642726097902823196.tmp\app.chameleon.sample.ChameleonLab\compile-6\extras\chameleonlab
         Link succeeded
         Linking succeeded -- 0,282s
      1,138s total -- Compile completed

Where do you think I'm doing it wrong? Please help me, thanks.

the.wizard

unread,
Jan 13, 2015, 7:37:15 AM1/13/15
to mv...@googlegroups.com
I almost forget, here is the link that discuss about mvp4g technique and gin technique : https://groups.google.com/forum/#!searchin/mvp4g/multiple$20view/mvp4g/LlBlaQpSxfA/Bbb1O-7jfOUJ
Thanks.


Pada Selasa, 13 Januari 2015 04.08.38 UTC+7, Frank Hossfeld menulis:

Frank Hossfeld

unread,
Jan 13, 2015, 4:09:54 PM1/13/15
to mv...@googlegroups.com
Hi Pada,

please can you change:

*---- CustomPresenter.java -----*/
@Presenter(view = CustomView.class)
public class CustomPresenter extends LazyPresenter<ICustomView, ICustomEventBus> implements ICustomPresenter {
private Integer secretNumber = 88;

into:

*---- CustomPresenter.java -----*/
@Presenter(view = ICustomView.class)
public class CustomPresenter extends LazyPresenter<ICustomView, ICustomEventBus> implements ICustomPresenter {
private Integer secretNumber = 88;

Does this help?

Frank

Am Dienstag, 13. Januar 2015 20:23:38 UTC+1 schrieb Lukas Schmidt:
- zitierten Text einblenden -

the.wizard

unread,
Jan 13, 2015, 8:43:13 PM1/13/15
to mv...@googlegroups.com
Hi Frank,
Yes, your reply really helped me. Thanks for your suggestion, now it's working perfectly.
Btw, I just want to discuss about the pros and cons of each technique. There was a thread before my thread that discuss about this, here it is: https://groups.google.com/forum/#!searchin/mvp4g/multiple$20view/mvp4g/LlBlaQpSxfA/Bbb1O-7jfOUJ
In that thread, it state that there are two technique to switching view of single presenter. The first one was called mvp4g technique, https://groups.google.com/forum/?fromgroups#!topic/mvp4g/rxMrurhFm8k. which is looks like my previous technique (override the setView and select view based on user agent). The second one was called GIN technique, https://groups.google.com/forum/?fromgroups#!topic/mvp4g/GNuREQspbdg and https://code.google.com/p/mvp4g/wiki/GinIntegration. These technique rely on gin to inject the correct view based on user agent.
  1. So what is the pros and cons from each technique? Please share your opinion.
  2. The technique that use GIN and the technique that you suggested to me, do they produce a lot of permutation when we compile our code?

Thank you so much for your help and suggestion, I really appreciate it.

Frank Hossfeld

unread,
Jan 14, 2015, 1:04:08 AM1/14/15
to mv...@googlegroups.com
Hi Pada,

that's great.

Btw, I think you do not need the DeviceDetector because mgwt.formfactor does the same.

I personally prefer the technique you are using now. This is the way google does it. (take a look at the mobilewebapp example). Ok, it will produce some more permutations, but this will only increase the compilation time. On the other side you'll get perfect optimized code for every browser and device.

Using GIN does not make sense because you need a third party lib for something that will also work without using it.

Also, there is no need to do some coding, as you do in the so called mvp4g technique. 

Frank
Reply all
Reply to author
Forward
0 new messages