Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Código limpio en la UI para Android, mejores pr...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Código limpio en la UI para Android, mejores prácticas.

Charla para el droidcon de Republica Dominicana, enfocada en generar mejores practicas y aplicaciones.

More Decks by Cristian Fabian Gómez Rojas

Other Decks in Programming

Transcript

  1. “ Las arquitecturas te ayudan a separar tu lógica de

    negocio de la presentación, pero, ¿Qué pasa con todo el código de la UI?
  2. “ El código limpio puede ser legible por una persona

    diferente al autor… tiene pruebas… usa nombres adecuados… cuenta con dependencias mínimas… tiene un API reducido…
  3. Codigo Limpio ▪ Lectura ▪ Pruebas ▪ Nombramiento ▪ Dependencias

    ▪ API reducido Estás seguro que tu código es limpio?
  4. Como facilitar la lectura? ▪ Claridad ▪ Consistencia ▪ Proposito

    ▪ Expresividad ▪ Estandarización (Formateador, Guia de estilos, Camel Case)
  5. Características del Código Android ▪ Separa las reglas de negocio

    de la presentación (MVx). ▪ Como manejar todo el código restante? ▪ La UI sigue siendo mucho código ▪ Buenas prácticas de Android no son buenas prácticas de Software. ▪ El código de UI también puede hablar con propósito.
  6. class MyActivity extends AppCompatActivity { @OnClick(R.id.button) public void onButtonAction(){ RequestTask

    task = new RequestTask(); task.exec(); } @OnClick(R.id.button1) public void onButton1Action(){ Reques1tTask task = new Request1Task(); task.exec(); } static class RequestTask() extends AsyncTask {...} static class Request1Task() extends AsyncTask {...} }
  7. class LoginActivity extends AppCompatActivity { @OnClick(R.id.login_button) public void onLoginButtonClicked() {

    String username = usernameField.content(); String password = passwordField.content(); loginAction.auth(username, password) .subscribe(result -> showHomeScreen(), t -> showErrorMessage() ); } @OnClick(R.id.login_facebook_button) public void onLoginFacebookButtonClicked() { facebookAction.auth().subscribe(result -> showHomeScreen(), t -> showErrorMessage() ); } }
  8. Testing UI ▪ Given/When/Then ▪ Espresso - Flebox layout by

    Google ▪ Chequeo de Intents ▪ Chequeo de información ▪ Navegación correcta ▪ Screenshots ▪ Inversión de dependencias - Fabio Collini / Adrian Catalan
  9. @RunWith(AndroidJUnit4.class) @MediumTest public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule

    = new ActivityTestRule<>(MainActivity.class); @Test @FlakyTest public void testAddFlexItem() { MainActivity activity = mActivityRule.getActivity(); FlexboxLayout flexboxLayout = (FlexboxLayout) activity.findViewById(R.id.flexbox_layout); assertNotNull(flexboxLayout); int beforeCount = flexboxLayout.getChildCount(); onView(withId(R.id.add_fab)).perform(click()); assertThat(flexboxLayout.getChildCount(), is(beforeCount + 1)); } }
  10. @RunWith(AndroidJUnit4.class) @MediumTest public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule

    = new ActivityTestRule<>(MainActivity.class); @Test @FlakyTest public void testAddFlexItem() { MainActivity activity = mActivityRule.getActivity(); FlexboxLayout flexboxLayout = (FlexboxLayout) activity.findViewById(R.id.flexbox_layout); assertNotNull(flexboxLayout); int beforeCount = flexboxLayout.getChildCount(); onView(withId(R.id.add_fab)).perform(click()); assertThat(flexboxLayout.getChildCount(), is(beforeCount + 1)); } }
  11. @RunWith(AndroidJUnit4.class) @MediumTest public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule

    = new ActivityTestRule<>(MainActivity.class); @Test @FlakyTest public void testAddFlexItem() { MainActivity activity = mActivityRule.getActivity(); FlexboxLayout flexboxLayout = (FlexboxLayout) activity.findViewById(R.id.flexbox_layout); assertNotNull(flexboxLayout); int beforeCount = flexboxLayout.getChildCount(); onView(withId(R.id.add_fab)).perform(click()); assertThat(flexboxLayout.getChildCount(), is(beforeCount + 1)); } }
  12. @RunWith(AndroidJUnit4.class) @MediumTest public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule

    = new ActivityTestRule<>(MainActivity.class); @Test @FlakyTest public void testAddFlexItem() { MainActivity activity = mActivityRule.getActivity(); FlexboxLayout flexboxLayout = (FlexboxLayout) activity.findViewById(R.id.flexbox_layout); assertNotNull(flexboxLayout); int beforeCount = flexboxLayout.getChildCount(); onView(withId(R.id.add_fab)).perform(click()); assertThat(flexboxLayout.getChildCount(), is(beforeCount + 1)); } }
  13. Rx es facil usar en Test ▪ Mock & Observable.just(...);

    ▪ Rx es honesto ▪ RxJava 2 es mas honesto ▫ Completable ▫ Flowable ▫ Observable ▫ dispose
  14. Acércate a la realidad ▪ Poder vs Deber ▪ Una

    sola palabra (evadir nombres compuestos) ▪ Evadir nombres muy genéricos (data, results, item, etc)
  15. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EditText et

    = (EditText) findViewById(R.id.editText); EditText et2 = (EditText) findViewById(R.id.editText2); Button bt = (Button) findViewById(R.id.button); Button bt2 = (Button) findViewById(R.id.button2); }
  16. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EditText et

    = (EditText) findViewById(R.id.editText); EditText et2 = (EditText) findViewById(R.id.editText2); Button bt = (Button) findViewById(R.id.button); Button bt2 = (Button) findViewById(R.id.button2); }
  17. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login_activity); EditText usernameField

    = (EditText) findViewById(R.id.login_username); EditText passwordField = (EditText) findViewById(R.id.login_password); Button loginButton = (Button) findViewById(R.id.login_button); Button facebookButton = (Button) findViewById(R.id.login_facebook_button); }
  18. Convenciones ▪ Views vs variables (usernameView vs username as String)

    ▪ Field, View, Button, Pager, List, Grid, etc. ▪ usernameEditText o usernameTextInputLayout? ▪ Ids con namespaces (login_username, login_button, login_something, etc) Dime tu intención y donde estás actuando...
  19. API

  20. @Override protected void onCreate(Bundle savedInstanceState) { ... usernameField.addTextChangedListener(new TextWatcher() {

    @Override public void beforeTextChanged(CharSequence s, int start, int count, int after){ username = s.toString(); check(); } ... }); passwordField.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after){ password = s.toString(); check(); } ... }); } private void check() { if(!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) loginButton.setEnabled(true); }
  21. @Override protected void onCreate(Bundle savedInstanceState) { ... usernameField.onChanged(username -> check(username,

    passwordField.content())); passwordField.onChanged(password -> check(usernameField.content(), password)); } private void check(String username, String password) { if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) loginButton.setEnabled(true); }
  22. XML

  23. <resources> <string name="login_username_field">...</string> <string name="login_password_field">...</string> <dimen name="login_title_margin_top">...</dimen> <style name="AppTheme.Login"/> <style

    name="AppTheme.Login.Field">...</style> <style name="AppTheme.Login.Button" parent="AppTheme.Common.Button">...</style> <style name="AppTheme.Login.Message">...</style> <style name="AppTheme.Login.Title">...</style> </resources>
  24. <resources> <string name="login_username_field">...</string> <string name="login_password_field">...</string> <dimen name="login_title_margin_top">...</dimen> <style name="AppTheme.Login"/> <style

    name="AppTheme.Login.Field">...</style> <style name="AppTheme.Login.Button">...</style> <style name="AppTheme.Login.Message">...</style> <style name="AppTheme.Login.Title">...</style> </resources>
  25. public class TextField extends android.support.v7.widget.AppCompatEditText { interface Before { void

    changed(String s); } interface On { void changed(String s); } interface After { void changed(String s); } private Before before; private On on; private After after; }
  26. public class TextField extends android.support.v7.widget.AppCompatEditText { public TextField(Context context, AttributeSet

    attrs) { super(context, attrs); addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (before != null) before.changed(content()); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (on != null) on.changed(content()); } @Override public void afterTextChanged(Editable s) { if (after != null) after.changed(content()); } }); } }
  27. Events ▪ RxBinding subscibe/unsubscribe ▪ Custom events ayudan a limpiar

    tu código, liberan la dependencia directa a los cambios de API.
  28. payerField.onContactSelected(new ContactFieldListener() { @Override public void onContactSelected(Contact contact){ paymentFrom(contact); }

    }); receiverField.onContactSelected(new ContactFieldListener() { @Override public void onContactSelected(Contact contact){ paymentTo(contact); } });
  29. Principales consideraciones ▪ Un codigo más simple te ayuda a

    encontrar problemas mas facilmente. ▪ Muchas clases y recursos, pero no muchas más que algunas librerías. ▪ Usa Proguard / Reducción de código ▪ #PerfMatters / #EnumMatters