Consistency is probably one of the best-known design principles. Consistent UIs are easy to use, easy to learn and frustration free. Nonetheless, they are also extremely easy to break! Just a few development iterations are enough to totally mess up your color palette or your icon sets. Yelp ships its experience across Android, iOS and Web apps used by millions of users. In this talk, you will get an insight into the challenges we face on a daily basis ensuring our visual consistency, and the solutions we adopted.
36. Theme
<resources>
<!—- This theme is the parent of all themes of Yelp's android apps. —->
<style name="YelpStyleguideTheme"/>
</resources>
37. Theme
<resources>
<!—- This theme is the parent of all themes of Yelp's android apps. —->
<style name="YelpStyleguideTheme" parent=“Theme.AppCompat.Light.DarkActionBar"/>
</resources>
38. Theme
<resources>
<!—- This theme is the parent of all themes of Yelp's android apps. —->
<style name="YelpStyleguideTheme" parent=“Theme.AppCompat.Light.DarkActionBar">
<item name="userPassportStyle">@style/UserPassport</item>
</style>
</resources>
45. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
46. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
public void setName(String name) {
mUserName.setText(name);
}
47. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
public void setName(String name) {
mUserName.setText(name);
}
public void setDescription(String description) {
if (TextUtils.isEmpty(description)) {
mDescription.setVisibility(GONE);
} else {
mDescription.setVisibility(VISIBLE);
mDescription.setText(description);
}
}
48. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
49. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
public UserPassport(final Context context) {
super(context);
init(context, null, 0);
}
public UserPassport(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, R.attr.userPassportStyle);
}
public UserPassport(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
50. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
public UserPassport(final Context context) {
super(context);
init(context, null, 0);
}
public UserPassport(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, R.attr.userPassportStyle);
}
public UserPassport(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
51. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
private void init(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
}
52. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
private void init(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.user_passport, this, true);
}
53. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
private void init(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.user_passport, this, true);
mUserName = (TextView) findViewById(R.id.user_name);
mDescription = (TextView) findViewById(R.id.description);
}
54. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
private void init(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.user_passport, this, true);
mUserName = (TextView) findViewById(R.id.user_name);
mDescription = (TextView) findViewById(R.id.description);
final TypedArray styles = context.obtainStyledAttributes(attrs,
R.styleable.UserPassport, defStyleAttr, 0);
}
55. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
private void init(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.user_passport, this, true);
mUserName = (TextView) findViewById(R.id.user_name);
mDescription = (TextView) findViewById(R.id.description);
final TypedArray styles = context.obtainStyledAttributes(attrs,
R.styleable.UserPassport, defStyleAttr, 0);
setName(styles.getString(R.styleable.UserPassport_userPassportName));
setDescription(styles.getString(
R.styleable.UserPassport_userPassportDescription));
}
56. public class UserPassport extends RelativeLayout {
private TextView mUserName;
private TextView mDescription;
private void init(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.user_passport, this, true);
mUserName = (TextView) findViewById(R.id.user_name);
mDescription = (TextView) findViewById(R.id.description);
final TypedArray styles = context.obtainStyledAttributes(attrs,
R.styleable.UserPassport, defStyleAttr, 0);
setName(styles.getString(R.styleable.UserPassport_userPassportName));
setDescription(styles.getString(
R.styleable.UserPassport_userPassportDescription));
styles.recycle();
}
96. Taking Screenshots with Espresso
public class ScreenshotViewActions {
public static ViewAction screenshot(final String folderName, final String fileName) {
return new ViewAction() {
};
}
}
97. Taking Screenshots with Espresso
public class ScreenshotViewActions {
public static ViewAction screenshot(final String folderName, final String fileName) {
return new ViewAction() {
// Other methods omitted.
@Override
public void perform(UiController uiController, View view) {
ScreenshotsUtil.takeScreenshot(folderName, fileName, view);
}
};
}
}
100. Sample Espresso Test
public class StarsViewActivityTests {
@Test
public void takeScreenshot() throws InterruptedException {
onView(withId(R.id.stars_view_4)).perform(setStarsNumber(4));
}
}
101. Sample Espresso Test
public class StarsViewActivityTests {
@Test
public void takeScreenshot() throws InterruptedException {
onView(withId(R.id.stars_view_4)).perform(setStarsNumber(4));
onView(withId(R.id.stars_view_5)).perform(setStarsNumber(5),
screenshot(FOLDER_NAME, "stars_with_text"));
}
}
102. Sample Espresso Test
public class StarsViewActivityTests {
@Test
public void takeScreenshot() throws InterruptedException {
onView(withId(R.id.stars_view_4)).perform(setStarsNumber(4));
onView(withId(R.id.stars_view_5)).perform(setStarsNumber(5),
screenshot(FOLDER_NAME, "stars_with_text"));
ScreenshotUtil.fullScreenshot(FOLDER_NAME, "stars_fullscreen");
}
}