One Activity to rule them all
How to architect a mobile app on the fast
&
What to avoid to keep it clean
The problem
● Android framework does not impose
structure/architecture
● Any sensible application needs some form of a
priori architectural thinking/setup
● Creating a heavily outlined and well-thought-out
architecture is often overkill for simple apps
Outline
● One screen = one fragment + one layout file
● Activity is used to orchestrate flow between
fragments/screens
● Activity also supplies all data needed to the
screens, and handles all API to underlying data,
network etc. layers
SAMF diagram
Pros & Cons
+ Quick setup
+ Modular
+ Easy to implement views across multiple screens
- Can be unwieldy with too many screens
- Complex UI flows hard to handle
- Dealing with state loss can be messy
Use for simple, contained screens without too many animations
// Get the code: https://github.com/olrandir/samf_3aadm
// com.olrandir.samf.fragments.HomeFragment
public class HomeFragment extends BaseFragment {
...
public interface HomeActions {
public SamfPerson getStoredPerson();
public void goToProfile();
}
}
// ==============================
// com.olrandir.samf.MainActivity
public class MainActivity
extends AppCompatActivity
implements HomeFragment.HomeActions
{
....
@Override
public SamfPerson getStoredPerson() {
return getUser();
}
@Override
public void goToProfile() {
transitionTo(new ProfileFragment());
}
}
The fragment defnes an
interface with thefunctionality it requires
The activity implements it
// com.olrandir.samf.fragments.HomeFragment
import com.olrandir.samf.models.SamfPerson;
public class HomeFragment extends BaseFragment {
....
@Override
public void onStart(){
super.onStart();
// Initialize our data
SamfPerson user = ((HomeActions)getActivity()).getStoredPerson();
String name = getString(R.string.home_text_greeting_nameUnknown);
// Fill in fields with initial data, if available
if( user != null && !TextUtils.isEmpty(user.getName()) ){
name = user.getName();
}
mGreeting.setText(
getString(R.string.home_text_greeting).replace("$1", name)
);
}
}
// com.olrandir.samf.fragments.ProfileFragment
public class ProfileFragment extends BaseFragment {
....
public interface ProfileActions {
public SamfPerson getStoredPerson();
public void setStoredPerson(SamfPerson person);
public void goToHome();
}
}
// ==============================
// com.olrandir.samf.MainActivity
public class MainActivity extends AppCompatActivity
implements ProfileFragment.ProfileActions,
HomeFragment.HomeActions
{
....
@Override
public void setStoredPerson(SamfPerson person) {
storeUser(person);
}
@Override
public void goToHome() {
transitionTo(new HomeFragment());
}
}
Note the
accumulation
of “implements”
clauses!
Activity views
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout ...>
<FrameLayout
android:id="@+id/mainactivity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FloatingActionButton
android:id="@+id/activity_button_edit"
... />
</RelativeLayout>
// com.olrandir.samf.MainActivity
public class MainActivity extends AppCompatActivity
implements ProfileFragment.ProfileActions, HomeFragment.HomeActions {
BaseFragment currentScreen;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Create first screen
if( currentScreen==null ){
currentScreen = new HomeFragment();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.mainactivity_content, currentScreen).commit();
// Add a backstack listener
getSupportFragmentManager().addOnBackStackChangedListener(
new OnBackStackChangedListener() {
public void onBackStackChanged() {
currentScreen = (BaseFragment) getSupportFragmentManager()
.findFragmentById(R.id.mainactivity_content);
}
}
);
}
}
Get a handle for thecurrent screen fragment
// com.olrandir.samf.MainActivity
public class MainActivity
extends AppCompatActivity
implements ProfileFragment.ProfileActions, HomeFragment.HomeActions,
View.OnClickListener {
BaseFragment currentScreen;
FloatingActionButton editButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Initialize views belonging to the main activity
editButton = (FloatingActionButton)
findViewById(R.id.activity_button_edit);
editButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if( currentScreen != null ){
currentScreen.onFABClick();
}
}
}
The activity creates the
view, then delegates its
interactions to the current
fragment
// com.olrandir.samf.fragments.HomeFragment
public class HomeFragment extends BaseFragment {
...
@Override
public void onFABClick() {
((HomeActions)getActivity()).goToProfile();
}
}
// ===========================================
// com.olrandir.samf.fragments.ProfileFragment
public class ProfileFragment extends BaseFragment {
...
@Override
public void onFABClick() {
showMessage(“You’re already in the profile!”);
}
}
Different fragments
can have different
actions for the button
// com.olrandir.samf.fragments.BaseFragment
public abstract class BaseFragment extends Fragment {
...
public abstract void onFABClick();
}
// ========================================
// com.olrandir.samf.fragments.HomeFragment
public class HomeFragment extends BaseFragment {
...
@Override
public void onFABClick() {
((HomeActions)getActivity()).goToProfile();
}
}
Fragments have interfaces;
the activity can defne its
API in a parent fragment all
screens inherit from
Mitigating
If you have many screens, the activity can become unwieldy (many
interfaces & functions to implement).
Solutions:
1. Use a single interface for all fragments; this allows you to be more
specific and careful with your Fragment-Activity API.
2. Break the pattern: use >1 activities, and group similar fragments
and 1 activity into flows
e.g. LoginActivity with LoginFragment and ForgotPasswordFragment
Luckily, it’s relatively easy to achieve this: have all activities extend a
parent Activity, implement lower layer APIs there.
Thank you!
dimitris@forky.gr | github: olrandir
code available: https://github.com/olrandir/samf_3aadm
Hungry?
Use promo DIMITRIS
for 1 free meal, first order only!
www.forky.gr

Android: the Single Activity, Multiple Fragments pattern | One Activity to rule them all

  • 1.
    One Activity torule them all How to architect a mobile app on the fast & What to avoid to keep it clean
  • 2.
    The problem ● Androidframework does not impose structure/architecture ● Any sensible application needs some form of a priori architectural thinking/setup ● Creating a heavily outlined and well-thought-out architecture is often overkill for simple apps
  • 3.
    Outline ● One screen= one fragment + one layout file ● Activity is used to orchestrate flow between fragments/screens ● Activity also supplies all data needed to the screens, and handles all API to underlying data, network etc. layers
  • 4.
  • 5.
    Pros & Cons +Quick setup + Modular + Easy to implement views across multiple screens - Can be unwieldy with too many screens - Complex UI flows hard to handle - Dealing with state loss can be messy Use for simple, contained screens without too many animations
  • 6.
    // Get thecode: https://github.com/olrandir/samf_3aadm // com.olrandir.samf.fragments.HomeFragment public class HomeFragment extends BaseFragment { ... public interface HomeActions { public SamfPerson getStoredPerson(); public void goToProfile(); } } // ============================== // com.olrandir.samf.MainActivity public class MainActivity extends AppCompatActivity implements HomeFragment.HomeActions { .... @Override public SamfPerson getStoredPerson() { return getUser(); } @Override public void goToProfile() { transitionTo(new ProfileFragment()); } } The fragment defnes an interface with thefunctionality it requires The activity implements it
  • 7.
    // com.olrandir.samf.fragments.HomeFragment import com.olrandir.samf.models.SamfPerson; publicclass HomeFragment extends BaseFragment { .... @Override public void onStart(){ super.onStart(); // Initialize our data SamfPerson user = ((HomeActions)getActivity()).getStoredPerson(); String name = getString(R.string.home_text_greeting_nameUnknown); // Fill in fields with initial data, if available if( user != null && !TextUtils.isEmpty(user.getName()) ){ name = user.getName(); } mGreeting.setText( getString(R.string.home_text_greeting).replace("$1", name) ); } }
  • 8.
    // com.olrandir.samf.fragments.ProfileFragment public classProfileFragment extends BaseFragment { .... public interface ProfileActions { public SamfPerson getStoredPerson(); public void setStoredPerson(SamfPerson person); public void goToHome(); } } // ============================== // com.olrandir.samf.MainActivity public class MainActivity extends AppCompatActivity implements ProfileFragment.ProfileActions, HomeFragment.HomeActions { .... @Override public void setStoredPerson(SamfPerson person) { storeUser(person); } @Override public void goToHome() { transitionTo(new HomeFragment()); } } Note the accumulation of “implements” clauses!
  • 9.
    Activity views <?xml version="1.0"encoding="utf-8"?> <RelativeLayout ...> <FrameLayout android:id="@+id/mainactivity_content" android:layout_width="match_parent" android:layout_height="match_parent"/> <FloatingActionButton android:id="@+id/activity_button_edit" ... /> </RelativeLayout>
  • 10.
    // com.olrandir.samf.MainActivity public classMainActivity extends AppCompatActivity implements ProfileFragment.ProfileActions, HomeFragment.HomeActions { BaseFragment currentScreen; @Override protected void onCreate(Bundle savedInstanceState) { ... // Create first screen if( currentScreen==null ){ currentScreen = new HomeFragment(); } getSupportFragmentManager().beginTransaction() .replace(R.id.mainactivity_content, currentScreen).commit(); // Add a backstack listener getSupportFragmentManager().addOnBackStackChangedListener( new OnBackStackChangedListener() { public void onBackStackChanged() { currentScreen = (BaseFragment) getSupportFragmentManager() .findFragmentById(R.id.mainactivity_content); } } ); } } Get a handle for thecurrent screen fragment
  • 11.
    // com.olrandir.samf.MainActivity public classMainActivity extends AppCompatActivity implements ProfileFragment.ProfileActions, HomeFragment.HomeActions, View.OnClickListener { BaseFragment currentScreen; FloatingActionButton editButton; @Override protected void onCreate(Bundle savedInstanceState) { ... // Initialize views belonging to the main activity editButton = (FloatingActionButton) findViewById(R.id.activity_button_edit); editButton.setOnClickListener(this); } @Override public void onClick(View v) { if( currentScreen != null ){ currentScreen.onFABClick(); } } } The activity creates the view, then delegates its interactions to the current fragment
  • 12.
    // com.olrandir.samf.fragments.HomeFragment public classHomeFragment extends BaseFragment { ... @Override public void onFABClick() { ((HomeActions)getActivity()).goToProfile(); } } // =========================================== // com.olrandir.samf.fragments.ProfileFragment public class ProfileFragment extends BaseFragment { ... @Override public void onFABClick() { showMessage(“You’re already in the profile!”); } } Different fragments can have different actions for the button
  • 13.
    // com.olrandir.samf.fragments.BaseFragment public abstractclass BaseFragment extends Fragment { ... public abstract void onFABClick(); } // ======================================== // com.olrandir.samf.fragments.HomeFragment public class HomeFragment extends BaseFragment { ... @Override public void onFABClick() { ((HomeActions)getActivity()).goToProfile(); } } Fragments have interfaces; the activity can defne its API in a parent fragment all screens inherit from
  • 14.
    Mitigating If you havemany screens, the activity can become unwieldy (many interfaces & functions to implement). Solutions: 1. Use a single interface for all fragments; this allows you to be more specific and careful with your Fragment-Activity API. 2. Break the pattern: use >1 activities, and group similar fragments and 1 activity into flows e.g. LoginActivity with LoginFragment and ForgotPasswordFragment Luckily, it’s relatively easy to achieve this: have all activities extend a parent Activity, implement lower layer APIs there.
  • 15.
    Thank you! dimitris@forky.gr |github: olrandir code available: https://github.com/olrandir/samf_3aadm Hungry? Use promo DIMITRIS for 1 free meal, first order only! www.forky.gr