I am currently working on a app and it might be used on all android devices. I find it really challenging to adjust my XML layout files according to various screen sizes. I have surfed a lot over this topic and found a useful doc at Developer site. The document is decent and provides enough information on what should be done for screen compatibility.
Questions :
1.If I have two different layouts in folders like res/layout-sw600dp and res/layout-sw720dp, will the app automatically decides which one of these layouts is to be used ?
2.Assuming that I prefer a ListView for handsets and GridView for Tabs as a Home Page display, how will I define my layouts and how will I refer them for UI ?
Any ideas on how I can pull off the 2nd question's feature will be highly appreciated. Thanks in advance.
You can defferenciate it in java by checking "hasHoneycomb", tabs will give tru as return value. One way is : you can set different layout from setcontentview() according to condition.
if(hasHoneycomb()) {
setcontentView(layout_for_tabs);
} else {
setcontentView(layout_for_phones);
}
1) Yes, it will, based on the screen size.
2) Give them different IDs and see which one is visible:
ListView mList = (ListView) findViewById( R.id.homeList );
GridView mGrid = (GridView) findViewById( R.id.homeGrid );
if( mList != null ) {
// set list adapter
} else if( mGrid != null ) {
// set grid adapter
} else {
// neither view exists...
}
Yes, app automatically decide which layout file to use for current device screen.
Give the ListView and GridView different ids. In code use findViewById() method when creating views.
If ListView found (findViewById(R.id.list) returned View) app is running on handset, otherwise (findViewById(R.id.list) returned null and findViewById(R.id.grid) returned View) app is running on tablet.
For question 1, Yes Android will automatically choose and decide the most suitable amongst the two.
For 2, here is link - Determine if the device is a smartphone or tablet?
Hope it helps. :)
Related
I'm testing a hybrid app, where each view has a web view.
In one of these web views I have a list of elements with the same attribute. They have the same xpath locator that is something like:
//h4[contains(#data-role, 'product-name')]
I want to create a list of these elements and iterate through them, count them, get their attributes.
In the documentation, I found two similar methods:
findElement(locator, value)
and
findMultipleElements(locator, value)
Though it's totally unclear to me how to use it. I tried to find examples on it but with no success.
Could someone help me with this?
Here is the solution that I have found.
#kaqqao is right that findMultipleItems call returns Atom<List<ElementReference>> that is not usable with onWebView() because there you have only withElement() that accepts either Atom<ElementReference> or just ElementReference
What you can do though is perform your action that find multiple items and just get results from your Atom. This is how it works internally if you check the source of doEval method inside Web.java for espresso.
val elements = with(AtomAction(findMultipleElements(
Locator.XPATH,
"YOUR_COMPLEX_XPATH"
), null, null)) {
onView(ViewMatchers.isAssignableFrom(WebView::class.java)).perform(this)
this.get()
}
This code will give you List<ElementMatcher>.
Then just run it as
elements.forEach {
onWebView().forceJavascriptEnabled().withElement(it).perform(webClick())
}
Can you try something like that? Since what you should care about is really the ElementReference and you can iterate the lsit returned from findMultipleElements with simple for/foreach statement:
yourList = findMultipleElements(locator, value);
yourList.size(); //this will get you the count of found elements with that locator
for(Atom<ElementReference> item : yourList ){
item.getAttribute...
//and whatever you want
}
I am trying to test my app which uses ViewPager. Each page contains fragments but these fragments are not always visible. I want to check visibility of a fragment in the currently visible page.
onView(withId(R.id.container_weather))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
But the problem is that espresso looks are all the pages not just the current page and I get the following error:
android.support.test.espresso.AmbiguousViewMatcherException: 'with id: eu.airpatrol.android:id/container_weather' matches multiple views in the hierarchy...
I had the same problem, however using the condition isCompletelyDisplayed() solved this problem as it only takes into account the on-screen views.
So, something like this should work:
onView(allOf(withId(R.id.container_weather), isCompletelyDisplayed()))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
Note: isDisplayed() works too in some cases but it also takes views off-screen into account and won't work if the ViewPager has any other page pr fragment loaded with the same view id.
Your tests are failing because of multiple elements with the same id. You can combine conditions using allOf(...). Then use isDisplayed() to check that matched view is visible on the screen. Below example can work:
onView(allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.container_weather)))
.check(matches(isDisplayed()));
Ran into this exact same problem. I was fortunate because the view hierarchies in my ViewPager can be easily identified by their siblings, so I was able to solve this using the hasSibling matcher, like so:
onView(
allOf(
hasSibling(withId(R.id.some_sibling)),
withId(R.id.field_to_test)
)
).perform(replaceText("123"));
Not a perfect solution as it can be slightly brittle, but in my case I think it was an acceptable compromise.
I had similar problem, where I was reusing the button layout and it was giving me a matches multiple views in the hierarchy exception.
So the easy work around I did was to create 2 different screens and have 2 different methods with different text.
Withdraw Screen:
public WithdrawScreen clickWithdraw() {
onView(allOf(withId(R.id.save_button), withText("Withdraw")))
.perform(click());
return this;
}
Deposit Screen:
public DepositScreen clickDeposit() {
onView(allOf(withId(R.id.save_button), withText("Deposit")))
.perform(click());
return this;
}
and in my tests, I create a new instance of both screens and call the above methods based on screen reference which is a bit easy to test for.
WithdrawScreen withdrawInstance = new WithdrawScreen();
withdrawInstance.clickWithdraw();
DepositScreen depositInstance = new DepositScreen();
depositInstance.clickDeposit();
The point was they were using same id - R.id.save_button for button and I was replacing text of button based on visibility of the fragment we are on.
Hope it helps.
I am trying to choose between two different layouts based on a value of a certain parameter and set that layout for my class.Both the layouts are having the same ids of all the views.If we dynamically allocate ids to the views of the two layout wont there be a ambiguity?When i tried practically I am getting a null pointer exception.Is this null pointer because of this ambiguity only??Can anybody please help.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// condition can be from getIntent() or from SharedPreferences or whatever
if(condition) {
setContentView(R.layout.activity_main);
} else {
setContentView(R.layout.activity_main_2);
}
// If they have the same components, but different layouts only
// You can use it as usual with findViewById(R.id.view_id);
Button btnExample = findViewById(R.id.btn_example);
}
I could't understand well what you asked. If i'm correct you asking if you can use the same id for a view on different layouts.
Yes you can. Unless you try to inflate those two layouts at the same time. Then you will have a problem.
You need to narrow you error.
if(condition) {
setContentView(R.layout.activity_main);
} else {
setContentView(R.layout.activity_main_2);
}
When you set different views, it may cause null pointer exception when any of the view is missing between this layouts.
So you should make sure that your findViewById returns not null. OR just add check when you try to use this views in the code.
Advanced....
Anyway if you already know all these things and have enough experience with all these stuffs, Try the belw listed libraries it will help you a lot
https://github.com/JakeWharton/butterknife
https://github.com/roboguice/roboguice
You can avoid "repetitive, cumbersome boilerplate coding parts. Indeed, we should focus on logic, not on meta programming".
These will help you to avoid all these findViewById...stuffs and you can slim your code...less bugs!
I'm new to Android and I'm building a simple application to start with. It consists of a client with three screens. In the first screen the user is prompted for an Ip to connect to a server (I use an EditText and a button). If the connection is successfully established, some data will be retrieved from the server and the client will show the data on a blank screen (I use a TextView). This would be the second screen. Then, the user could ask the server for detailed information about any data that has been retrieved from the server, which would be the third screen (I use a TextView again).
The problem is that I don't know what's the best way to go about it. I have currently one activity and one XML file containing all the components of the view (EditText, button, TextView). Until now, I've been using setVisibility(View.GONE);to hide certain components depending on the screen the user is in. (For example in the first screen I have to hide both TextViews).
One of the problems I'm facing is that when I put the phone in a horizontal position the components I had hidden show up again. I don't know if hiding views is the ideal thing to do for my purpose.
I've thought that maybe I should use more than one activity, shouldn't I?
I really appreciate any help you can give me to structure my first app.
I would definitely recommend splitting up your App into multiple Activities/Fragments. Depending on how big the logic for each screen gets you will be glad you did it later on because each Activity only has one responsibility.
Look at your mail app for example. You got your List Activity showing you all your mails and then when you select one it starts the Detail Activity showing you the content of your mail. Each Activity is only responsible for one thing which make each one easier to write and maintain.
It also simplifies your layout definitions because each one only contains the relevant parts.
Seems like this is coming up a lot. Android destroys and recreates and Activity when the configuration changes. Screen rotation is part of the orientation. In order to avoid that, the Activity is responsible for retaining state. The mechanisms given for that are the onCreate and onSaveInstanceState. In your example, you could do something like the following:
int uiPhase = 1;
#Override
void onCreate( Bundle data ) {
uiPhase = data.getInt( "uiPhase", 1 );
// inflate layout
setPhase( uiPhase );
}
// invoke the following each time your screen changes
void setPhase( int newPhase ) {
uiPhase = newPhase;
switch( uiPhase ) {
case 1: // show UI elements for first screen, hide others
break;
case 2: // show UI elements for second screen, hide others
break;
case 3: // show UI elements for third screen, hide others
break;
}
}
#Override
void onSaveInstanceState( Bundle data ) {
data.put( "uiPhase", uiPhase );
}
I didn't want to complicate the pattern above too much, but a good method for setting visibility is as follows:
phase1view.setVisibility( uiPhase == 1 ? View.VISIBLE : View.GONE );
phase2view.setVisibility( uiPhase == 2 ? View.VISIBLE : View.GONE );
phase3view.setVisibility( uiPhase == 3 ? View.VISIBLE : View.GONE );
That pulls the repetition in the setPhase method quite a bit together.
Set button visibility to GONE (button will be completely "removed" -- the buttons space will be available for another widgets) or INVISIBLE (button will became "transparent" -- its space will not be available for another widgets):
use in place of
setVisibility(View.GONE)
change to
setVisibility(View.INVISIBLE) and try
i use different Layouts for different Screensizes and Devices. I use Fragments with specific Layout Folders.
The Concept is great, for Tablets and Devices with a Large Screen i place a Layout file in
layout-sw600dp and Android manages to deliver the right layout on the different devices.
What Bugs me is: How can i find out what Layout is used inside my Code.
My Fragments needs slightly different Codes for the different Layouts.
In General whats the Best Practice to separate Custom Layout Programming Logic inside my Fragments/Activities?
My approach now is kind of hacky and not in sync with the different Layout folders.
private boolean isTabletDevice() {
if (android.os.Build.VERSION.SDK_INT >= 11) { // honeycomb
// test screen size, use reflection because isLayoutSizeAtLeast is
// only available since 11
Configuration con = getResources().getConfiguration();
try {
Method mIsLayoutSizeAtLeast = con.getClass().getMethod("isLayoutSizeAtLeast", int.class);
Boolean r = (Boolean) mIsLayoutSizeAtLeast.invoke(con, 0x00000004); // Configuration.SCREENLAYOUT_SIZE_XLARGE
return r;
} catch (Exception x) {
x.printStackTrace();
return false;
}
}
return false;
}
and then
if(isTabletDevice()) {
//findViewById(R.id.onlyInTabletLayoutButton);
}else{
//
}
This is the method I use personally:
In each layout, I add a Tag to the root of the layout, and make sure that all of the layout roots have the same id. So for example, I'll have a layout that goes something like:
<RelativeLayout
android:id="#+id/rootView"
android:tag="landscapehdpi">
<!-- Rest of layout -->
</RelativeLayout>
And then have another one like:
<RelativeLayout
android:id="#+id/rootView"
android:tag="portraitmdpi">
<!-- Rest of layout -->
</RelativeLayout>
Then once the layout has been inflated, I use:
View rootView = (View) findViewById(R.id.rootView);
This returns the layout root currently in use. Now to determine which layout it is exactly and run the appropriate code, I use a series of if-else blocks:
String tag = rootView.getTag().toString();
if(tag.equals("landscapehdpi"))
{
//Code for the landscape hdpi screen
}
else if(tag.equals("portraitmdpi"))
{
//Code for the portrait mdpi screen
}
//And so on...
So basically using this you can know which layout has been loaded at runtime, and run the appropriate code.
I think you are looking for the same solution as this question here,
How can I detect which layout is selected by Android in my application?.
if you would like to see the best answer, there are two available options.
First one is to use config for your values folder and from there get the String from your xml file and cross check it. (To use it as a flag).
https://stackoverflow.com/a/11670441/603744
And the next one is to set Tag to your layouts, and get the tag from your code to find out which tag it prints and based on that find the layout what it has used. But you also have to note that there is a little bug in this approach. But I haven't tired them yet.
https://stackoverflow.com/a/11205220/603744