As a little example: Take a website where you land on a login page and after a successful login getting directed to the main page where on the left side you'd have a menu and on the right side a content are where everything gets loaded when clicking a link in the menu. In the menu I'd have multiple stores where each store would have its own settings. Those settings would be initialized with an id to determine which content the view should display.
For the history mapper I guess one would have something like this:
/#LoginPlace:login
/#MainPlace:home
/#MainPlace:storeSettings?id=<id>
/#MainPlace:userSettings
etc..
In [2] it says
"In order to be accessible via a URL, an Activity needs a corresponding Place."
Which sounds to me like I should have a LoginActivity and a MainActiviy since after all LoginActivity is the first Place I arrive when I come to my website and MainActivity is the place I go when I am successfully logged in.
I'd have someting like this:
public class LoginActivity extends AbstractActivity implements LoginView.Presenter {
// ..
private void onLoginSuccess() {
this.clientFactory.getPlaceController().goTo(new MainPlace());
}
}
public class MainActivity extends AbstractActivity implements MainView.Presenter {
// ..
}
and of course have respective view LoginView and MainView.
This is how AppActivityMapper.getActivity() would look like:
#Override
public Activity getActivity(Place place) {
if (place instanceof LoginPlace) {
return new LoginActivity((LoginPlace) place, clientFactory);
} else if (place instanceof MainPlace) {
return new MainActivity((MainPlace) place, clientFactory);
}
return null;
}
So far so good but how would I implement the MenuView and the MainContentView?
I want to be able to click on menu items in the MenuView and update MainContentView and generate place tokens accordingly like:
/#MainPlace:home
/#MainPlace:storeSettings?id=<id>
/#MainPlace:userSettings
But I have no idea how to do that and how I would for example initialize my activity StoreSettingsActivity with the given id. I believe that I would need another MainActivityMapper extends ActivityMapper that now should control the place changes initiated by clicking on menu links but I just can't figure out how that could work.
I hope my question is clear, please let me know if you need some more information.
I think I once did something similar, it was not perfect because I had to deal with an existing architecture but it worked.
Here is what I did:
When a link from the menu is click execute placeController.goTo(new MainPlace(id));
In your ActivityMapper, return your MainActivity in which you set the id of the MainPlace.
In your MainActivity, in the start method call a method that will initiate your MainContentView depending on the id of MainPlace you set earlier in the MainActivity.
I am pretty sure there is a better way of doing this, but it works and it can be a start that might help you to find a better solution.
Related
Well, I will try to explain the problem as well as possible.
I'm using vaadin 8 and I'm using java. I'm doing an app like Youtube.
First Of All I have two important class for the problem. 1º user_header. 2º main_user_page.
On the first class (1º). I have some code integrated with components vaadin that creates a header for youtube with An image and nickname.
public class Zona_de_busqueda_UR extends Zona_de_busqueda_UR_ventana {
public Zona_de_busqueda_UR() {
imagenCabeceraURZ.setSource(new ExternalResource(parametros.getMiniaturaUsuarioNavega()));
apodoCabeceraURZ.setCaption(parametros.getApodoUsuarioNavega());
}
The two atributtes are components vaadin, and I'm setting the text to show on the app.
On the second class (2º). is one page, where (after I initiate session) I can see a normal page with videos and the header of the class (1º). THIS CLASS IS WHAT I RUN AND THIS CLASS CALL THE OTHER WHERE THE ATRIBUTTES ARE EJECUTED.
public class Pagina_inicio_UR extends Pagina_inicio_UR_ventana implements View {
Zona_de_busqueda_UR cabeceraZUR = new Zona_de_busqueda_UR();
public Pagina_inicio_UR() {
horizontalLayout.addComponent(cabeceraZUR);
}
In this class, only call the other class what you can see.
The problem is that the value of the components is showing where I refresh the app, but this would be after I initiate session. I show an example with photos.
First Of all, I initiate session.
![a busy cat(https://raw.githubusercontent.com/jmr779/images/master/primero.PNG)
And then, The header is without information...
![a busy cat(https://raw.githubusercontent.com/jmr779/images/master/segundo.PNG)
Now, I click F5 on my browser or refresh the page and I can see the information but I want see the information without having to click F5 or refresh.
![a busy cat(https://raw.githubusercontent.com/jmr779/images/master/tercero.PNG)
Thanks for the help.
I am wondering if there is any solution for method dependency of this object and the supper object. I am now working in android, and overriding the onBackPressed method.
I have created a class called ActivityContainer, used to handle universal behaviors. the logic of the onBackPressed of this class is
Close the menu if the menu is currently opening
Prompt if going to close the app, yes to close, no or no action to stay
Then, I have an other class called FragmentActivityContainer extends ActivityContainer, used to handle universal behaviors that used in fragment. the logic of the onBackPressed of this class id
Close the menu if the menu is currently opening
Back the previous fragment if changed fragments in content area
Prompt if going to close the app, yes to close, no or no action to stay
the dependency is messy. Let's take a look for ActivityContainer first
#Override
public void onBackPressed(){
if(this.menu != null && this.menu.isShowing()){
this.menu.close();
} else {
this.promptQuite();
}
}
nice and clear, works fine.
Let's have a look for FragmentActivityContainer
#Override
public void onBackPressed(){
if(this.menu != null && this.menu.isShowing()){
this.menu.close();
} else if(this.fragmentManager.getBackStackEntryCount() > 0){
super.getParent().onBackPressed();
} else{
this.promptQuite();
}
}
it works, but eagerly. I would like to use super.onBackPressed() in FragmentActivityContainer but since back to previous fragment is in the middle between menu close and prompt quite, there is no way for me to do that. What is the best solution to solve this dependency problem?
I'm doing some selenium work at the office and with some recent changes to the pages I was automating I'm trying to figure out the best way to do it. I'm not exactly sure how to explain everything but I'll give it a try.
I have a payment form with different 'steps'. Each step ask you to fill some info and they all have a 'continue' button to go to the next step. Since every pageObject shares the same button I created an abstract class with that logic and made ever page extend from that one.
Something like this (removing all the code that's not relevant to the question):
public abstract class AbstractPaymentPage<T extends AbstractPaymentPage> extends AbstractPageObject {
#FindBy(id = "continueBtn")
private WebElement continueButton;
public AbstractPaymentPage(WebDriver driver) {
super(driver);
}
public T nextStep() {
continueButton.click();
return this.initPageObject(getNextStepClass());
}
protected abstract Class<T> getNextStepClass();
}
And the child classes are defined like this:
public class PersonalInfoPage extends AbstractPaymentPage<PaymentCountryPage> {
public PersonalInfoPage(WebDriver driver) {
super(driver);
}
#Override
protected Class<PaymentCountryPage> getNextStepClass() {
return PaymentCountryPage.class;
}
}
The typical usage for this is would be to call PersonalInfoPage.nextStep() which will click on the continue button and initialize all the webElements on the next page.
Now, this works and I'm actually pretty happy with it but it only works if the current page can tell exactly which page is coming up next. In this case 'PersonalInfoPage' knows that the next one is 'PaymentCountryPage'. But with these new changes there are scenarios in which the flow will be PageA->PageB->PageC->PageD, others that can be PageA->PageD and others could be PageA->PageC->PageD.
What do you guys think would be the best to handle something like this without ending with a ton of code duplication? Was thinking that maybe I could use generic variables instead of the actual destination class for the 'getNextStepClass' but I'm not sure how that would work.
Dunno if it's clear enough to get some help but feel free to ask anything you guys think needs more explaining and all that.
This complex scenario can be handled with introducing some self-loading logic into the PageObjectModel. Further more some IoC with the LoadableComponent and the SlowLoadableComponent can be of a great help here. In short - let the last page know how to load itself through calling the previous page (if applicable in your domain).
Instead of
PageA->PageB->PageC->PageD
you'll get something like
PageD-load->PageC-load->PageB-load->PageA
Clear advantage here will be the reusable received flow of control when it comes to avoid
ending with a ton of code duplication
I have three activities in my app
A login activity
A main activity
A detail activity
I want to use espresso to test a sequence of events: click the login button on the login activity, which opens the main activity, and then click a list item in main activity, which opens detail activity, and then click another button in the detail activity. I started by creating this simple test, to get a reference to the listview:
public class LoginActivityTest extends ActivityInstrumentationTestCase2<LoginActivity> {
public LoginActivityTest() {
super(LoginActivity.class);
}
#Override
public void setUp() throws Exception {
super.setUp();
getActivity();
}
public void testSequence() throws Exception {
// Login
onView(withId(R.id.button_log_in)).perform(click());
// Check if MainActivity is loaded
onView(withId(R.id.container)).check(matches(isDisplayed()));
// Check if Fragment is loaded
onView(withId(R.id.list)).check(matches(isDisplayed()));
}
}
On the mainActivity onCreate() method I load a fragment like this:
getFragmentManager().beginTransaction()
.add(R.id.container, mListFragment)
.commit();
The ListFragment fragment has a list (R.id.list), but still the test fails with a NoMatchingViewException:
android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: com.tests.android.development:id/list
What am I doing wrong?
A note from the documentation for onView:
Note: the view has to be part of the view hierarchy. This may not be
the case if it is rendered as part of an AdapterView (e.g. ListView).
If this is the case, use Espresso.onData to load the view first.
To use onData to load the view, you need to check for instances of whatever your adapter is in the ListView. In other words, if your listview uses a Cursor adapter, you can try this:
onData(allOf(is(instanceOf(Cursor.class)))).check(matches(isDisplayed()));
It is important to note that the above will only pass if your listview contains at least one item. It is a good idea to have one test where an item exists, and one test where an item does not.
For more information on how to check for data that does exist, see here.
For more information on how to check for data that does not exist in an adapter, see here.
In the current version (Espresso 2.2.2) this exception is always appended with a View Hierarchy: clause that lists all the views available to match. Stroll through that and check if you can find your list.
As an alternative: check out android-sdk\tools\uiautomatorviewer.bat (or .sh) which takes a snapshot from the current screen and hierarchy. Put a breakpoint on your list matching line and check with the viewer if the list is there. If you find the list, there may be a timing issue in the test. Maybe it didn't wait enough, check out more about IdlingResources.
I have a login page and the main page. After login, I have
public void onSuccess(String result) {
// go to the next page
SDM_Mailer page = new SDM_Mailer();
RootPanel.get().remove(0);
RootPanel.get().add(page);
}
However RootPanel does not accept an EntryPoint object! add() only accepts a Widget! Ok, so I extend Widget
public class SDM_Mailer extends Widget implements EntryPoint {
But now when I try to edit SDM_Mailer in the GWT Designer, it gives this error:
So exactly how do you create multiple pages, that are not all in the same giant class file or using tab? I know I've done this before but don't remember and with an older version.
You can adopt Activities and Places.
As mentioned to the official gwt project site:
The Activities and Places framework allows you to create bookmarkable URLs within your application, thus allowing the browser's back button and bookmarks to work as users expect.
Activity
An activity simply represents something the user is doing. An Activity contains no Widgets or UI code.
Place
A place is a Java object representing a particular state of the UI. A Place can be converted to and from a URL history token
Source: http://www.gwtproject.org/doc/latest/DevGuideMvpActivitiesAndPlaces.html
You usually only have one EntryPoint from there you add different widgets (which can represent your different pages) and manage them.
A very simple way to do this is for a small app can be to use .setVisible(true) and setVisible(false) on different widgets that represent pages, but this is not a good method in the long run.
You could also have a container widget in which you add whatever widget you want to display and then when you want to put a new widget in it you clear it.
container.clear();
container.add(widget)
The suggestions above are ok for small apps but aren't great when your app has a lot of pages (views). A popular way to manage pages (views) is to use MVP Activities and Places. It's a lot of overhead but is scalable and works well.
Extending Widget doesn't magically make something representable in HTML. Widgets provide graphical representations of something in HTML, that is, a way to render themselves into HTML, this is ussualy achieved by implementing UIBinders. I strongly suggest you take the MVP approach and use Activities, Places and Views. check the official documentation on how to do it, it's simpler than you may think.
If you are working with Eclipse, the GWT plugin does most of the boilerplate. You can watch this video on how to use it.
This is what I ended up doing:
package com.example.client;
import java.util.logging.Logger;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.RootPanel;
public class Controller implements EntryPoint {
private static Controller instance;
private static final Logger log = Logger.getLogger(Controller.class.getName());
// I have a feeling GWT does not respect private constructors, or else it uses some other voodoo.
private Controller(){}
public static Controller getInstance() {
if (instance == null) instance = new Controller();
return instance;
}
#Override
public void onModuleLoad() {
String token = History.getToken();
log.info("****************************** token:"+token);
History.addValueChangeHandler(new ValueChangeHandler<String>() {
#Override
public void onValueChange(ValueChangeEvent<String> event) {
navigate(event.getValue());
} // onValueChange
});
if (token == null || token.length() == 0) History.newItem(Login.TOKEN); // no token
else navigate(token); // restore app state
}
private static void navigate(String token) {
RootPanel rootPanel = RootPanel.get("gwtApp");
if (rootPanel.getWidgetCount() > 0) rootPanel.remove(0); // clear the page
if (Login.TOKEN.equals(token)) {
Login page = Login.getInstance();
page.onModuleLoad();
} else if (MainApp.TOKEN.equals(token)) {
MainApp page = MainApp.getInstance();
page.onModuleLoad(); // display the page
// page.setAuthenticated(true);
// page.setUsername(email);
}
}
} // Controller
In your *.gwt.xml file:
<entry-point class='com.example.client.Controller' />
Now when you want to go to a new page:
History.newItem(Login.TOKEN);
This seems quite familiar and is probably what I came up with a few years ago.