Android Application Model II
Android Application Model II
2
Recap
• Android Framework
• Activities
• General UI:
– Layouts, Handler methods
– Widgets, Custom UI classes, Handling within activity
• Specialized UIs:
– Menus and the Action Bar
– Special Activities – Preferences
• UI for larger screens: Fragments
3
The Activity Lifecycle
• Android runtime manages Activities
• Activities have a “lifecycle” consisting of
states: from creation until death
• Standard (lifecycle) methods on the
activity are invoked at each state change
(can test by rotating device)
4
Activity States
• Created: Born to run
• Active: Working 9 to 5
• Paused: I’m about to break
• Resumed: Back to work
• Stopped: Obscured by clouds, vulnerable
5
Activity Transitions
• Created ⟹ Active
• Active ⟺ Paused
• Paused ⟹ Stopped ⟹ Active
• Stopped ⟹ Killed
6
Timing of
Activity
Lifecycle
Methods
7
Lifecycle Methods
• onCreate(Bundle savedInstanceState): create
views, (re) initialize state
• onStart(): Restore transient state; one-time processing
• onResume(): Session-specific processing, restore
transient state
• onPause(): Save persistent data, release resources,
quickly! Last method guaranteed to be called.
• onStop(): Called optionally by runtime
• onDestroy(): If finish() is called, or object is being
temporarily destroyed. Distinguish via isFinishing().
8
Transient State Management Methods
• onSaveInstanceState(...): Called
before onPause(); use to save transient
state.
• onRestoreInstanceState(...): Called
after onStart() and onResume(); use to
restore state.
9
Transient State Management: Java
// MapsActivity.java
public class MapsActivity extends AppCompatActivity /* . . . */ {
private MapView mMapView;
private String whereAmIString = null;
. . .
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
if (whereAmIString != null) {outState.putString(WHERE_AM_I_STRING, whereAmIString);}
}
. . .
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
whereAmIString = savedInstanceState.getString(WHERE_AM_I_STRING);
if (whereAmIString != null) { mEditLocation.setText(whereAmIString); }
}
10
}
Interaction Across Activities
• Activity 1: onPause()
• Activity 2: onCreate(), onStart(),
onResume()
• Activity 1: onStop() – if it is obscured
11
Starting Activities: Intents and
Intent Filters
• Message posted to the Android runtime to launch
an activity; matched against IntentFilter of
activity in AndroidManifest.xml file
• Encourages activity reuse among applications
• Uniform mechanism for launching internal and
external activities
• Enables loose coupling between caller, responder
12
Intent and Intent Filter Components
13
Intent Filters: Examples
<!-- AndroidManifest.xml -->
<intent-filter> <!== for SplashScreen activity ==>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter> <!== for Login activity ==>
<action
android:name="com.wiley.fordummies.androidsdk.tictactoe.Login">
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
14
Intent Invocations: Examples: Java
• Matching component name: startActivity(new Intent(
"com.wiley.fordummies.androidsdk.tictactoe.Login"));
• Direct invocation: startActivity(new Intent(
getActivity().getApplicationContext(), SettingsActivity.class));
• Passing data: public void sendScoresViaEmail() {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.putExtra(Intent.EXTRA_SUBJECT,
"Look at my AWESOME TicTacToe Score!");
emailIntent.setType("plain/text");
emailIntent.putExtra(Intent.EXTRA_TEXT, firstPlayerName +
18
Services
• Activities for background, long-term processing (e.g., email,
music, synchronization)
• Local: Accessible by a single app.
• Remote (aka bound): Accessible by all apps on the device
– See: http://developer.android.com/guide/topics/fundamentals/
services.html#CreatingBoundService
• Comparison with threads:
– Coarser-grained processing
– Lifecycle separated from launching activity
– More resources consumed
– More respected by OS
19
Service Example: Java
public class MediaPlaybackService extends Service {
MediaPlayer player;
@Override
public IBinder onBind(Intent intent) {/*...*/}
public void onCreate() {
player = MediaPlayer.create(this, R.raw.sampleaudio); player.setLooping(true);
}
public int onStartCommand(Intent intent, int flags, int startId) { /* ... */
Bundle extras = intent.getExtras();
String audioFileURIString = extras.getString("URIString");
Uri audioFileURI = Uri.parse(audioFileURIString);
player.reset(); player.setDataSource(this.getApplicationContext(),
audioFileURI);
player.prepare(); player.start(); /* ... */
}
public void onDestroy() { player.stop(); }
} 20
Service Example: Kotlin
class MediaPlaybackService : Service() {
override fun onBind(intent: Intent): IBinder? {/*. . .*/}
override fun onCreate() {
player = MediaPlayer.create(this, R.raw.sample_audio)
player.apply { isLooping = true } }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val extras = intent.extras
if (extras != null) {
val audioFileURIString = extras.getString("URIString")
val audioFileURI = Uri.parse(audioFileURIString)
player.reset()
player.setDataSource(this.applicationContext, audioFileURI)
player.prepare()
player.start()
} }
override fun onDestroy() { player.stop() }
}
21
Service Invocation
Java Kotlin
. . . . . .
Intent musicIntent = val musicIntent = Intent(
new Intent( activity.applicationContext,
getApplicationContext(), MediaPlaybackService::
MyPlaybackService.class); class.java)
musicIntent.putExtra( musicIntent.putExtra(
"URIString", "URIString",
mAudioFileURI.toString()); mAudioFileUri.toString())
activity.startService(
getActivity().
musicIntent)
startService(musicIntent);
. . .
. . .
22
Outline
• Activity Lifecycle
• Services
• Persistence
• Content Providers
23
Saving Persistent Data – Files
• Same as in any Java or Kotlin app
• Internal storage (Linux file system)
– Local to app
– Destroyed upon uninstall
• External storage (e.g., SD card)
– Stored to SD card or flash memory (device-dependent)
– Shared among applications
– E.g.:
Java Kotlin
File imageFile = val imageFile =
new File(imageFilePath); File(imageFilePath)
24
Saving Persistent Data – SQLite
• http://sqlite.org: “Most widely deployed
SQL database engine in the world”
– Lightweight, transactional
• Helper class for database creation
• Methods for operating on SQL statements:
– Creating a statement
– Binding data
– Executing
25
SQLite Example: Account Model Class
Java Kotlin
// Account.java // Account.kt
public class Account { data class Account(val name: String,
private String mName, mPassword; val password: String) {
// We don't need anything here!
public Account(String name,
}
String password) {mName = name;
mPassword = password; }
26
SQLite Example: DB Schema: Java
// AccountDbSchema.java
public class AccountDbSchema {
public static final class AccountsTable {
public static final String NAME = "accounts";
27
SQLite Example: Account CursorWrapper
Java Kotlin
// AccountCursorWrapper.java // AccountCursorWrapper.kt
public class AccountCursorWrapper class AccountCursorWrapper(cursor: Cursor) :
extends CursorWrapper { CursorWrapper(cursor) {
public AccountCursorWrapper(
Cursor cursor) { super(cursor); } val account: Account
public Account getAccount() { get() {
String name = getString( val name = getString(
getColumnIndex( getColumnIndex(
AccountsTable.Cols.NAME)); AccountsTable.Cols.NAME))
String password = getString(
getColumnIndex( val password = getString(
getColumnIndex(
AccountsTable.Cols.PASSWORD));
AccountsTable.Cols.PASSWORD))
Account account =
new Account(name, password);
return Account(name, password)
}
return account;
}
} 28
}
SQLite Example: Account Singleton: Java
// AccountSingleton.java
public class AccountSingleton { // Continued . . .
private static AccountSingleton sAccount;
public void addAccount(Account account) {
private AccountDbHelper mDbHelper;
ContentValues contentValues =
private SQLiteDatabase mDatabase;
getContentValues(account);
private static final String INSERT_STMT =
. . .
"INSERT INTO " + AccountsTable.NAME +
SQLiteStatement statement =
"(name, password) VALUES (?, ?)" ; /* . . . */
mDatabase.compileStatement(INSERT_STMT);
private AccountSingleton(Context context) {
statement.bindString(1,
mDbHelper = new AccountDbHelper(
contentValues.getAsString(
context.getApplicationContext());
AccountsTable.Cols.NAME));
mDatabase = mDbHelper.getWritableDatabase();
statement.bindString(2,
}
contentValues.getAsString(
private static ContentValues getContentValues(
AccountsTable.Cols.PASSWORD));
Account account) {
statement.executeInsert();
ContentValues values = new ContentValues();
}
values.put(AccountsTable.Cols.NAME,
public void deleteAllAccounts() { /* ... */
account.getName());
values.put(AccountsTable.Cols.PASSWORD, mDatabase.delete(AccountsTable.NAME,
account.getPassword()); null, null);
return values; }
}
}
// Also used with Kotlin
// Continued . . .
29
SQLite Example: Helper Class: Java
// AccountDbHelper.java
public class AccountDbHelper extends SQLiteOpenHelper {
private Context mContext;
private static final String DATABASE_NAME = "TicTacToe.db";
private static final int DATABASE_VERSION = 1;
public AccountDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); }
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE " + AccountsTable.NAME + "(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
AccountsTable.Cols.NAME + " TEXT, " +
AccountsTable.Cols.PASSWORD + " TEXT" + ")");}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, /* ... */) {
Log.w("Example", "Example: upgrading DB; dropping/recreating tables.");
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + AccountsTable.NAME);
onCreate(sqLiteDatabase); } 30
}
SQLite Example: Helper Class: Kotlin
// AccountDbHelper.kt
class AccountDbHelper(context: Context) : SQLiteOpenHelper(
context, DATABASE_NAME, null, DATABASE_VERSION) {
private lateinit var mContext: Context
override fun onCreate(sqLiteDatabase: SQLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE " + AccountsTable.NAME + "(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " + AccountsTable.Cols.NAME +
" TEXT, " + AccountsTable.Cols.PASSWORD + " TEXT" + ")")}
companion object {
private val DATABASE_NAME = "TicTacToe.db"
private val DATABASE_VERSION = 1 }
}
31
SQLite Example: DB Operations: Java
// AccountFragment.java
private void createAccount() {
FragmentActivity activity = getActivity();
String username = mEtUsername.getText().toString(),
String password = mEtPassword.getText().toString();
String confirm = mEtConfirm.getText().toString();
if (activity != null) {
if ((password.equals(confirm)) && (!username.equals("")) && (!password.equals("")) &&
(!confirm.equals(""))) {
AccountSingleton singleton = AccountSingleton.get(activity.getApplicationContext());
Account account = new Account(username, password);
singleton.addAccount(account);
Toast.makeText(activity.getApplicationContext(), "New record inserted",
Toast.LENGTH_SHORT).show();
} else if ((username.equals("")) || (password.equals("")) || (confirm.equals(""))) {
Toast.makeText(activity.getApplicationContext(), "Missing entry",
Toast.LENGTH_SHORT).show();
} else if (!password.equals(confirm)) {
/* Show error message */
} else { /* Error handling */ }
}
} 32
SQLite Example: DB Operations: Kotlin
// AccountFragment.kt
private fun createAccount() {
val username = mEtUsername.text.toString()
val password = mEtPassword.text.toString()
val confirm = mEtConfirm.text.toString()
if (password == confirm && username != "" && password != "" && confirm != "") {
val singleton = AccountSingleton.get(activity?.applicationContext)
val account = Account(username, password)
singleton.addAccount(account)
Toast.makeText(activity?.applicationContext, "New record inserted",
Toast.LENGTH_SHORT).show()
} else if (username == "" || password == "" || confirm == "") {
Toast.makeText(activity?.applicationContext, "Missing entry",
Toast.LENGTH_SHORT).show()
} else if (password != confirm) {
/* Error handling */
} else { /* Error handling */ }
}
33
Cloud Persistence
• Cloud data stores useful when app users need
to read other users’ written data
• Example: Google’s Firebase
– Non-relational data store (high availability)
– Realtime Database: persists (key, value) data in a
shared JSON tree
– Cloud Firestore: supports more flexible persistence
(document-based approach)
– More info: http://firebase.google.com
34
Outline
• Activity Lifecycle
• Services
• Persistence
• Content Providers
35
Sharing Content – Content Providers
• Standard content providers:
– Contacts, dictionary, media
• Referred to by URI prefix: content://
– Standard providers
• Permission must be requested in
manifest:
<uses-permission android:name=
"android.permission.READ_CONTACTS"/>
@Override
public void onRequestPermissionsResult(. . . ) {
if (requestCode == PERMISSION_REQUEST_READ_CONTACTS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { showContacts(); }
else { /* Show error message */} }
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Put the result Cursor in the adapter for the ListView
mCursorAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) { mCursorAdapter.swapCursor(null); }
} // End of Fragment
39
Content Provider Example: Kotlin (1)
// ContactsFragment.kt
class ContactsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
private lateinit var mContactsListView: ListView
companion object { . . .
private val PROJECTION = arrayOf(ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
private val PERMISSION_REQUEST_READ_CONTACTS = 1
private val FROM_COLUMNS = arrayOf(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
private val TO_IDS = intArrayOf(R.id.contact_info) }
private lateinit var mCursorAdapter: SimpleCursorAdapter
42
Even More?!
• Yes!
• More widgets, styles and themes
• Maps
• Email, media, telephony
• Sensors
43
Summary
• Activities: “Single screen” of content that user sees
– Can be paused, resumed by users
– Managing Activity lifecycle is crucial!
• Services: Long-running tasks
• Persistence:
– Files (internal, external storage)
– (Local) SQLite database
– Look at https://developer.android.com/guide/topics/
data/data-storage.html for info about Room ORM (easier to use
than SQLiteOpenHelper)
• Content Providers: mechanism for sharing data 44