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

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

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