Android Application Design im Praxischeck

96
@mobileLarson @_openKnowledge Lars Röwekamp | CIO New Technologies App Design im Praxis-Check

description

Android Application Design im Praxischeck Speaker: Lars Röwekamp Spätestens seit Android 4 ist Google der Sprung vom Smartphone aufs Tablet gelungen und somit ein echter Konkurrent für die iOS-Welt entstanden. Unmengen an APIs machen es dem Android-Entwickler einfach, die vielen Frameworkfeatures, wie Maps, Contacts, Kalender etc., in eigene Apps zu integrieren. Wie kommt es dann aber, dass nach wie vor ein Großteil der am Markt existierenden Apps eher schlecht als recht funktionieren und entsprechende Bewertungen bekommen? Die Session zeigt typische Pitfalls im Android Application Design und zeigt, wie man mit einer gut durchdachten Architektur und Ergonomie bei den Endanwendern punkten kann.

Transcript of Android Application Design im Praxischeck

Page 1: Android Application Design im Praxischeck

@mobileLarson @_openKnowledge

Lars Röwekamp | CIO New Technologies

App Design im Praxis-Check

Page 2: Android Application Design im Praxischeck

Kunde droht mit Auftrag ...

Page 3: Android Application Design im Praxischeck

„Can you do this?“

Page 4: Android Application Design im Praxischeck
Page 5: Android Application Design im Praxischeck

Can we do this?

Page 6: Android Application Design im Praxischeck

„Can you do this?“

Page 7: Android Application Design im Praxischeck

„Yes, of course we can!“

„Can you do this?“

Page 8: Android Application Design im Praxischeck

„But, lots of pitfalls ahead.“

„And, lots of work to do.“

Page 9: Android Application Design im Praxischeck

35.000+ Produkte

5.000+ Änderungen p.m.

2000+ Produktgruppen

Tablets & Smartphones

15.000+ Bilder

In-App Navigation

Komplexe Suche

Page 10: Android Application Design im Praxischeck

Pack mas’!

Page 11: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Daten

Lösung: No problèmo!

Page 12: Android Application Design im Praxischeck

Leider doch. 50MB (+2x2GB)

Page 13: Android Application Design im Praxischeck

> 50 MB (+ 2 x 5 GB)> APK Limit von 50 MB> Main Expension File (max. 2 GB)> Patch Expension File (max. 2 GB) !> Google Support via API> „automatischer“ Download im Hintergrund> Ablage in <shared-storage>/Android/…!

> Google Play Store Limits

Problem: Sehr, sehr viele Daten

Page 14: Android Application Design im Praxischeck

> 50 MB (+ 2 x 5 GB)> APK Limit von 50 MB> Main Expension File (max. 2 GB)> Patch Expension File (max. 2 GB) !> Google Support via API> „automatischer“ Download im Hintergrund> Ablage in <shared-storage>/Android/…!

> Google Play Store Limits

Problem: Sehr, sehr viele Daten

Page 15: Android Application Design im Praxischeck

> 50 MB (+ 2 x 5 GB)> APK Limit von 50 MB> Main Expension File (max. 2 GB)> Patch Expension File (max. 2 GB) !> Google Support via API> „automatischer“ Download im Hintergrund> Ablage in <shared-storage>/Android/…!

> Google Play Store Limits

Problem: Sehr, sehr viele Daten

Page 16: Android Application Design im Praxischeck

> 50 MB (+ 2 x 5 GB)> APK Limit von 50 MB> Main Expension File (max. 2 GB)> Patch Expension File (max. 2 GB) !> Google Support via API> „automatischer“ Download im Hintergrund> Ablage in <shared-storage>/Android/…!

> Google Play Store Limits

Problem: Sehr, sehr viele Daten

Page 17: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Daten

Lösung: DB Download ...

Page 18: Android Application Design im Praxischeck

> 200 MB+> Wifi Check inkl. optional Loading > Inkrementeller Download inkl. Resume

!> einige Minuten

> Vorab Hinweis auf „Ladezeit“ anzeigen> Working Progress Feedback inkl. „Step X of Y“> Loading Progress Feedback inkl. „X von Y“

> „manueller“ DB Download

Problem: Sehr, sehr viele Daten

Page 19: Android Application Design im Praxischeck

> DB Download > Check for WiFi

<!-- WiFi check: Declare Wifi as needed -->! <manifest ...>!

    <uses-feature android:name="android.hardware.wifi" ! android:required=["true"|"false"] />!    ...! </manifest>

Problem: Sehr, sehr viele Daten

Page 20: Android Application Design im Praxischeck

> DB Download > Check for WiFi only// WiFi check: check for existing WiFi!

! ConnectivityManager cm = (ConnectivityManager) ! getSystemService(Context.CONNECTIVITY_SERVICE);!! ! State wifi = ! cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)!

! ! ! ! .getState();!! if (wifi == NetworkInfo.State.CONNECTED || wifi == NetworkInfo.State.CONNECTING) {!

// WiFi is available!! } else { ! ...

Problem: Sehr, sehr viele Daten

Page 21: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Daten

// WiFi check: check for WiFi detail info! WifiManager wifiManager = (WifiManager) ! getSystemService(Context.WIFI_SERVICE);!! WifiInfo wifiInfo = wifiManager.getConnectionInfo();! SupplicantState wifiState = ! wifiInfo.getSupplicantState();!! if (wifiState == SupplicantState.COMPLETED) {!

// WiFi is available - get some wifiInfo details! int linkSpeed = wifiInfo.getLinkSpeed(); ...!! } else { ! ...

> DB Download > Check for WiFi Details

Page 22: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Daten

// WiFi check: check for WiFi detail info! WifiManager wifiManager = (WifiManager) ! getSystemService(Context.WIFI_SERVICE);!! WifiInfo wifiInfo = wifiManager.getConnectionInfo();! SupplicantState wifiState = ! wifiInfo.getSupplicantState();!! if (wifiState == SupplicantState.COMPLETED) {!

// WiFi is available - get some wifiInfo details! int linkSpeed = wifiInfo.getLinkSpeed(); ...!! } else { ! ...

> DB Download > Check for WiFi Details

Page 23: Android Application Design im Praxischeck

> DB Download > Incremental Loading// Step 1: Prepare download with DownloadManager!

DownloadManager.Request downloadRequest = new ! DownloadManager.Request(Uri.parse(DOWNLOAD_URI));!

! // set additional optional information: ! // Title, Description, MimeType, ExternalFileDir, ...!

! // allow download via Mobile or Wifi! downloadRequest.setAllowedNetworkTypes( DownloadManager.Request.NETWORK_WIFI | ! DownloadManager.Request.NETWORK_MOBILE);!

! // do not show the file in "Downloads"! downloadRequest.setVisibleInDownloadsUi(false);

Problem: Sehr, sehr viele Daten

Page 24: Android Application Design im Praxischeck

> DB Download > Incremental Loading!// Step 2: Start download with DownloadManager!

...! // register broadcast receiver for DOWNLOAD_COMPLETE! IntentFilter intentFilter = new IntentFilter(! DownloadManager.ACTION_DOWNLOAD_COMPLETE);! registerReceiver(receiver, intentFilter);!

! // Enqueue download! downloadId = downloadManager.enqueue(downloadRequest);

!!!!

Problem: Sehr, sehr viele Daten

Page 25: Android Application Design im Praxischeck

> DB Download > Incremental Loading// Step 3: Do some stuff after download has finished !

     BroadcastReceiver  receiver  =  new  BroadcastReceiver()  {  !            @Override              public  void  onReceive(Context  ctx,  Intent  intent)  {                                            //  1.  DownloadManager.ACTION_DOWNLOAD_COMPLETE?                        //  2.  ID  =  DownloadManager.EXTRA_DOWNLOAD_ID                      //  3.  query  DownloadManager  for  ID                      //  4.  DownloadManager.STATUS_SUCCESSFUL?                      //  5.  do  some  stuff  with  download               }        };  

Problem: Sehr, sehr viele Daten

Page 26: Android Application Design im Praxischeck

> DB Download > Showing Progress!!// show progress via DownloadManager (easy version)!

public void showDownload(View view) {!        Intent intent = new Intent();!        intent.setAction( DownloadManager.ACTION_VIEW_DOWNLOADS);!        startActivity(i);!   }

Problem: Sehr, sehr viele Daten

Page 27: Android Application Design im Praxischeck

> DB Download > Showing Progress// show progress via own Activity (eXtended version)!

public class ProgressActivity extends Activity {!! !

private ProgressBar mProgress; // or ProgressDialog!private int mProgressStatus = 0;!private Handler mHandler = new Handler();!

!protected void onCreate(Bundle icicle) {! super.onCreate(icicle);! setContentView(R.layout.progressbar_activity); ! mProgress = (ProgressBar)findViewById(R.id.pb);!

// Start some work in background thread ! doWorkAndUpdateProgressBarInBackground(); !

} …!}

Problem: Sehr, sehr viele Daten

Page 28: Android Application Design im Praxischeck

> DB Download > Showing Progress! // doWorkAndUpdateProgressBarInBackground() ! new Thread(new Runnable() {!! public void run() {!! while (mProgressStatus < 100) {!! mProgressStatus = checkDonwloadManagerStatus(); !!! // Update progress bar in UI thread!! mHandler.post(new Runnable() {!! public void run() {! ! mProgress.setProgress(mProgressStatus);!! }!! });!! }! }!}).start();

Problem: Sehr, sehr viele Daten

Page 29: Android Application Design im Praxischeck

> DB Download > Showing Progress <!-- Spinning wheel -->!  <ProgressBar!     style="@android:style/Widget.ProgressBar.Large"!     ... /> !! <!-- Progress bar from 0 .. 100 -->!  <ProgressBar!     style="@android:style/Widget.ProgressBar.Horizontal"!     ... />

Problem: Sehr, sehr viele Daten

Page 30: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Daten

Lösung 2: DB Download +

Page 31: Android Application Design im Praxischeck

> ca. 10 MB> Lazy Loading Ansatz zum Füllen der DB > Nur Basisdaten initial laden/vorhalten > Produktdetails und Bilder „as needed“

!> Unterstützte Funktionen

> Home Page Angebote anzeigen> Kategorien anzeigen> Suchen1), Filtern, ...

> DB Download+

Problem: Sehr, sehr viele Daten

Page 32: Android Application Design im Praxischeck

> ca. 10 MB> Lazy Loading Ansatz zum Füllen der DB > Nur Basisdaten initial laden/vorhalten > Produktdetails und Bilder „as needed“

!> Unterstützte Funktionen

> Home Page Angebote anzeigen> Kategorien anzeigen> Suchen1), Filtern, ...

> DB Download+ 1) Online-/Offline-Suche

Problem: Sehr, sehr viele Daten

Page 33: Android Application Design im Praxischeck

> DB Download+

Problem: Sehr, sehr viele Daten

Page 34: Android Application Design im Praxischeck

> DB Download+

Problem: Sehr, sehr viele Daten

Page 35: Android Application Design im Praxischeck

> Picasso (Square / komfortable API)> UrlImageViewHelper (sehr schnell)> Volley (Google / gut durchdacht)> Android Universal Image Loader (most popular)!

> „Do it yourself“ vs. „3rd Party“

Problem: Sehr, sehr viele Daten

Page 36: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Updates

Lösung: Update-Protokoll

Page 37: Android Application Design im Praxischeck

> ca. 5000 Änderungen p.M. > Modification XML Info zum „Start-up“ laden > Neue/geänderte/gelöschte Produkte/Bilder

!> Protokoll (XML basiert)

> Gelöschte Produkte/Bilder löschen> Neue Produkte/Bilder laden> Geänderte Produkte/Bilder laden

> Update Protokoll

Problem: Sehr, sehr viele Updates

Page 38: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Updates

Lösung 2: Update-Protokoll+

Page 39: Android Application Design im Praxischeck

> ca. 5000 Änderungen p.M. > Modification Info zum „Start-up“ laden > Produktdetails „as needed“

!> Protokoll (JSON basiert)

> Neue Produkte als Dummy anlegen> Gelöschte Produkte markieren> Geänderte Produkte markieren > Aufräumen „on-the-fly“

> Update Protokoll+

Problem: Sehr, sehr viele Updates

Page 40: Android Application Design im Praxischeck

> ca. 5000 Änderungen p.M. > Modification Info zum „Start-up“ laden > Produktdetails „as needed“

!> Protokoll (JSON basiert)

> Neue Produkte als Dummy anlegen> Gelöschte Produkte markieren> Geänderte Produkte markieren > Aufräumen „on-the-fly“

> Update Protokoll+

Problem: Sehr, sehr viele Updates

Page 41: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Updates

> JSON

Page 42: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Updates

> JSON

Page 43: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Updates

> JSON

Page 44: Android Application Design im Praxischeck

Problem: Sehr, sehr viele Updates

> JSON

Page 45: Android Application Design im Praxischeck

Problem: Lesbare Datenbank

Lösung: brauche ich eine?

Page 46: Android Application Design im Praxischeck

Problem: Lesbare Datenbank

Page 47: Android Application Design im Praxischeck

Problem: Lesbare Datenbank

Lösung: Encryption

Page 48: Android Application Design im Praxischeck

> DB im Android Filesystem> DB Verschlüsselung via SQLCipher > 256-bit AES Encryption für SQLite> Proxies für wichtigsten Android DB Klassen

!> SQLCipher anwenden

> Step 1: Imports setzen> Step 2: SQLCipher Libs laden> Step 3: alles wie sonst auch

> Encryption

Problem: Lesbare Datenbank

Page 49: Android Application Design im Praxischeck

> Encryption// Step 1: import sqlcipher!import info.guardianproject.database.! sqlcipher.SQLDatabase; !!// Trigger database initialization!public class SqlCypherActivity extends Activity {!

...! }

Problem: Lesbare Datenbank

Page 50: Android Application Design im Praxischeck

> Encryption// Trigger database initialization!public class SqlCypherActivity extends Activity {!

! ! @Override! public void onCreate(Bundle savedInstanceState) {! super.onCreate(savedInstanceState);! setContentView(R.layout.main); ! initializeSQLCipher();! }! ...!

}

Problem: Lesbare Datenbank

Page 51: Android Application Design im Praxischeck

> Encryption// Trigger database initialization!public class SqlCypherActivity extends Activity {!

! private void InitializeSQLCipher() {!

// Step 2: Load SQLCipher Libs! SQLiteDatabase.loadLibs(this);!

!// Step 3: Business as usual!

...!} !...!!

}

Problem: Lesbare Datenbank

Page 52: Android Application Design im Praxischeck

Problem: Diversifikation

Lösung: Ignorieren

Page 53: Android Application Design im Praxischeck

4.310 %

4.217 %

4.135,3 %

4.015,2 %

2.319 %

2.22.33.x4.04.14.24.34.4

Version Problem: Diversifikation

Page 54: Android Application Design im Praxischeck

xlarge4,9 %large

7,7 %

middle79 %

small8 %

smallmiddlelargexlarge

Größe Problem: Diversifikation

Page 55: Android Application Design im Praxischeck

xxhdpi12 %

xhdpi20,7 %

hdpi34,6 %

mdpi22 %

ldpi9 %

ldpimdpitvdpihdpixhdpixxhdpi

Problem: Diversifikation Auflösung

Page 56: Android Application Design im Praxischeck

Lösung 2: Teilweise ignorieren

Problem: Diversifikation

Page 57: Android Application Design im Praxischeck

4.310 %

4.217 %

4.135,3 %

4.015,2 %

2.319 %

2.22.33.x4.04.14.24.34.4

Version Problem: Diversifikation

Page 58: Android Application Design im Praxischeck

Lösung 3: Unified UI

Problem: Diversifikation

Page 59: Android Application Design im Praxischeck

> Unified UI

Problem: Diversifikation

Page 60: Android Application Design im Praxischeck

> Unified UI

Problem: Diversifikation

Page 61: Android Application Design im Praxischeck

> wäre schön, wären da nicht die Versionen !> Step 1: wenn möglich kompatibel bleiben > Step 2: wenn möglich Support Library > Step 3: wenn möglich 3rd Party Lösungen > Step 4: wenn möglich „Weiche“ !> Step 5: WTF! Verschiedene APKs !

!

> Unified UI

Problem: Diversifikation Version

Page 62: Android Application Design im Praxischeck

> unterstützt Abwärtskompabilität > v4 für Android 1.6 und höher > v7 für Android 2.1 und höher > v8 für Android 2.2 und höher > v13 für Android 3.2 und höher !

!

!

!

> Unified UI via Support Libraries

Problem: Diversifikation Version

Page 63: Android Application Design im Praxischeck

> Fragment (v4) > NotificationCombat (v4) > LocalBroadcastManager (v4) > ViewPager (v4) > DrawerLayout (v4) > SlidingPaneLayout (v4) > Loader (v4) !

!

> Unified UI via Support Libraries

Problem: Diversifikation Version

Page 64: Android Application Design im Praxischeck

> ActionBar (v7) > ActionBarActivity (v7) > ShareActionProvider (v7) > GridLayout (v7) > MediaRouter (v7) > RenderScript (v8) > FragmentCombat (v13) !

!

> Unified UI via Support Libraries

Problem: Diversifikation Version

Page 65: Android Application Design im Praxischeck

> ActionBarSherlock (Android 2.x) !

> Unified UI via 3rd Party Libraries

Problem: Diversifikation Version

Page 66: Android Application Design im Praxischeck

> Unified UI via „Versions-Weiche“

Version

!// check version of current device!

int apiVersion = android.os.Build.VERSION.SDK_INT;! if (apiVersion >= android.os.Build.VERSION_CODES.FROYO){! // Do something for froyo and above versions! } else{! // do something for phones running an SDK before froyo! }

Problem: Diversifikation

Page 67: Android Application Design im Praxischeck

> Unified UI via „Feature-Weiche“

Version

!// check version of current device!

PackageManager packageManager = this.getPackageManager();!! if (packageManager.hasSystemFeature(! PackageManager.FEATURE_NFC)) {!

// NFC feature is available !! ! ...!! } else { ! // NFC feature is NOT available !! ! ...!! }

Problem: Diversifikation

Page 68: Android Application Design im Praxischeck

Lösung 4: Multi-Device UI

Problem: Diversifikation

Page 69: Android Application Design im Praxischeck

> Multi-Device UI

Problem: Diversifikation

Page 70: Android Application Design im Praxischeck

> Multi-Device UI

Problem: Diversifikation

> UI Fragments als „Building Blocks“ > UI Activity Layout als „Block Assembler“ > UI Activity Class als „Block Assembler Logic“ !> UI Ressources als „Switch“ !

Größe

Page 71: Android Application Design im Praxischeck

> Multi-Device UI

Problem: Diversifikation Größe

Page 72: Android Application Design im Praxischeck

> Activity Modul a.k.a. Sub Activity > inkl. eigener UI > inkl. eigenem Lifecycle !> benötigt immer eine umliegende Activity > Lifecycle passt sich Activity-Lifecycle an

> Fragment

Problem: Diversifikation Größe

Page 73: Android Application Design im Praxischeck

> Size: small, normal, large, xlarge > Density: ldpi, mdpi, hdpi, xhdpi > Orientation: landscape, portrait

> Designation: sw, w, h, ...dp !

> Ressources

Problem: Diversifikation Größe

Page 74: Android Application Design im Praxischeck

> Size: small, normal, large, xlarge > Density: ldpi, mdpi, hdpi, xhdpi > Orientation: landscape, portrait

> Designation: sw, w, h, ...dp !

> Ressources

Problem: Diversifikation

Deprecated

Added

Größe

Page 75: Android Application Design im Praxischeck

Größe

> Multi-Device UI in Aktion

Problem: Diversifikation

Page 76: Android Application Design im Praxischeck

Größe

> Multi-Device UI in Aktion

Problem: Diversifikation

Page 77: Android Application Design im Praxischeck

Problem: In-App Navigation

Lösung: In-App Navigation ;-)

Page 78: Android Application Design im Praxischeck

> Activity Stack

Problem: In-App Navigation

Page 79: Android Application Design im Praxischeck

> Activity Stack

> Intent Flags> FLAG_ACTIVITY_NEW_TASK> FLAG_ACTIVITY_SINGLE_TOP> FLAG_ACTIVITY_CLEAR_TOP

> Launch Mode> standard> singleTop> singleTask> singleInstance

Problem: In-App Navigation

Page 80: Android Application Design im Praxischeck

> Activity Stack

Problem: In-App Navigation

Page 81: Android Application Design im Praxischeck

> Back vs. Up

Problem: In-App Navigation

Page 82: Android Application Design im Praxischeck

> Back vs. Up> „Back“ berücksichtigt Activity Stack > „Up“ berücksichtigt Use Case Stack

!> In-App Navigation

> Up-Navigation via In-App Buttons> Manipulation des Activity Stacks> Für Home und „Main Activities“

> In-App Navigation

Problem: In-App Navigation

Page 83: Android Application Design im Praxischeck

Problem: In-App Navigation

Page 84: Android Application Design im Praxischeck

Problem: In-App Navigation

Page 85: Android Application Design im Praxischeck

Problem: In-App Navigation

Page 86: Android Application Design im Praxischeck

Problem: In-App Navigation

Deep Dive

Page 87: Android Application Design im Praxischeck

Problem: In-App Navigation!!@Override

public void onCreate(Bundle savedInstanceState) {! super.onCreate(savedInstanceState);!!! setContentView(R.layout.main);!!! ActionBar actionBar = getActionBar();!! actionBar.setDisplayHomeAsUpEnabled(true);!! ...!}

Step 1: Enable „Up Navigation“

Page 88: Android Application Design im Praxischeck

Problem: In-App Navigation!!@Override!public boolean onOptionsItemSelected(MenuItem item) {! switch (item.getItemId()) {!! // app icon in action bar clicked: go home! case android.R.id.home:! Intent i = new Intent(this, MainActivity.class);! i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);! startActivity(intent);! return true;!! default:! return super.onOptionsItemSelected(item);! } !}

Step 2: Realize „Up Navigation“

Page 89: Android Application Design im Praxischeck

> … ziemlich viel Aufwand, oder? > … was ist mit „Deep-Dive“ Intents?

> Eigentlich ganz einfach, aber …

Problem: In-App Navigation

Page 90: Android Application Design im Praxischeck

Problem: In-App Navigation!!!<activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> ... </intent-filter> </activity> !<activity android:name=".ResultActivity" android:parentActivityName=".MainActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity"/> </activity>

Step 1: Enable „Up Navigation“

Page 91: Android Application Design im Praxischeck

Problem: In-App Navigation!!@Override!public boolean onOptionsItemSelected(MenuItem item) {! switch (item.getItemId()) {!! // app icon in action bar clicked: go home! case android.R.id.home:! Intent i = new Intent(this, MainActivity.class);! i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);! startActivity(intent);! return true;! ... ! default:! return super.onOptionsItemSelected(item);! } !}

Step 2: Realize „Up Navigation“

Page 92: Android Application Design im Praxischeck

> Einsprung von außen „mitten“ in die App> Navigation Stack wird künstlich aufgebaut a.k.a. Synthetic Back Stack!> Was ist mit dem fehlenden Kontext? „Music Details“ navigiert zurück zur „Music Liste“, aber zu welcher?

> „Deep-Dive“ Intents

Problem: In-App Navigation

Page 93: Android Application Design im Praxischeck

Problem: In-App Navigation!!@Override

public void onPrepareNavigateUpTaskStack(! TaskStackBuilder builder) {!! // retrieve intent for manipulation! int position = ...; ! Intent intent = builder.getIntentAt(position);!! // manipulate intent ! intent.putExtra(INTENT_SPECIFIC_INFO, specificInfo); ! ...!}

Step 3: Manipulate Intent Data

Page 94: Android Application Design im Praxischeck

@mobileLarson @_openKnowledge

Lars Röwekamp | CIO New Technologies

Thanks! Questions?

Page 95: Android Application Design im Praxischeck

Demo

Page 96: Android Application Design im Praxischeck