Android Application Model II
CSE 5236: Mobile Application DevelopmentInstructor: Adam C. Champion, Ph.D.Course Coordinator: Dr. Rajiv RamnathReading: Big Nerd Ranch Guide, Chaps. 3, 5 (Activities); Chap. 28 (Services); Chap. 14 (DB); Chap. 15 (Contacts)
1
Outline
• Activity Lifecycle• Services• Persistence• Content Providers
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: Fragments3
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 ActivityLifecycle 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;. . .
@Overrideprotected 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
• Target – fully qualified name (string), direct reference
• Action – standard or custom (string)• Data (URI)• Category of the target object
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 + " score is " + scorePlayerOne + " and " + secondPlayerName + " score is " + scorePlayerTwo);startActivity(emailIntent);
} 15
Intent Invocations: Examples: Kotlin• Matching component name: startActivity(Intent(
"com.wiley.fordummies.androidsdk.tictactoe.Login"))
• Direct invocation: startActivity(Intent(activity.applicationContext, SettingsActivity::class.java))
• Passing data: fun sendScoresViaEmail() {val emailIntent = Intent(Intent.ACTION_SEND)
emailIntent.putExtra(Intent.EXTRA_SUBJECT,
"Look at my AWESOME TicTacToe Score!")
emailIntent.type = "plain/text"
emailIntent.putExtra(Intent.EXTRA_TEXT, mFirstPlayerName +
" score is " + mScorePlayerOne + " and " + mSecondPlayerName +
" score is " + mScorePlayerTwo)+-
startActivity(emailIntent)
} 16
Tasks and Activity Stacks
• Task: a (conceptual) container for sets of “like-minded” activities
• Roughly corresponds to a Linux process• Activities are stacked in tasks• Activities can move across tasks• Task is requested by calling intent or
selected based on taskaffinity attribute in AndroidManifest.xml file
17
Outline
• Activity Lifecycle• Services• Persistence• Content Providers
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 J by OS
19
Service Example: Javapublic 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: Kotlinclass 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. . .
Intent musicIntent =
new Intent(getApplicationContext(),MyPlaybackService.class);
musicIntent.putExtra("URIString",mAudioFileURI.toString());
getActivity().startService(musicIntent);
. . .
Kotlin. . .
val musicIntent = Intent(activity.applicationContext,MediaPlaybackService::
class.java)musicIntent.putExtra(
"URIString",mAudioFileUri.toString())
activity.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.:
24
Java KotlinFile imageFile =
new File(imageFilePath);val imageFile =
File(imageFilePath)
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// Account.javapublic class Account {
private String mName, mPassword;
public Account(String name, String password) {mName = name;
mPassword = password; }
public String getName() { . . . }public String getPassword() { ... }
/* equals(), hashCode(), toString() */}
Kotlin// Account.kt
data class Account(val name: String,val password: String) {
// We don't need anything here!}
26
Kotlin data classes automatically generate getters, setters, equals(), hashCode(), and toString()
SQLite Example: DB Schema: Java
// AccountDbSchema.javapublic class AccountDbSchema {
public static final class AccountsTable {public static final String NAME = "accounts";
public static final class Cols {public static final String NAME = "name";public static final String PASSWORD = "password";
}}
}
// Also used with Kotlin
27
SQLite Example: Account CursorWrapper
Java// AccountCursorWrapper.javapublic class AccountCursorWrapper
extends CursorWrapper {public AccountCursorWrapper(
Cursor cursor) { super(cursor); }public Account getAccount() {
String name = getString(getColumnIndex(
AccountsTable.Cols.NAME));String password = getString(
getColumnIndex(AccountsTable.Cols.PASSWORD));
Account account = new Account(name, password);
return account;}
}
Kotlin// AccountCursorWrapper.ktclass AccountCursorWrapper(cursor: Cursor) :
CursorWrapper(cursor) {
val account: Accountget() {
val name = getString(getColumnIndex(
AccountsTable.Cols.NAME))
val password = getString(getColumnIndex(
AccountsTable.Cols.PASSWORD))
return Account(name, password)}
}28
SQLite Example: Account Singleton: Java
// AccountSingleton.java
public class AccountSingleton {private static AccountSingleton sAccount;private AccountDbHelper mDbHelper;private SQLiteDatabase mDatabase;private static final String INSERT_STMT ="INSERT INTO " + AccountsTable.NAME +"(name, password) VALUES (?, ?)" ; /* . . . */
private AccountSingleton(Context context) {mDbHelper = new AccountDbHelper(context.getApplicationContext());
mDatabase = mDbHelper.getWritableDatabase();
}private static ContentValues getContentValues(Account account) {ContentValues values = new ContentValues();values.put(AccountsTable.Cols.NAME,account.getName());
values.put(AccountsTable.Cols.PASSWORD,account.getPassword());
return values;
}
// Continued . . .
// Continued . . .
public void addAccount(Account account) {ContentValues contentValues =getContentValues(account);
. . . SQLiteStatement statement =mDatabase.compileStatement(INSERT_STMT);
statement.bindString(1, contentValues.getAsString(AccountsTable.Cols.NAME));
statement.bindString(2,contentValues.getAsString(AccountsTable.Cols.PASSWORD));
statement.executeInsert();
}public void deleteAllAccounts() { /* ... */
mDatabase.delete(AccountsTable.NAME, null, null);
}}
// Also used with Kotlin29
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); }
@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE " + AccountsTable.NAME + "(" +"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
AccountsTable.Cols.NAME + " TEXT, " + AccountsTable.Cols.PASSWORD + " TEXT" + ")");}
@Overridepublic 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" + ")")}
override fun onUpgrade(database: SQLiteDatabase, /* ... */) {Log.w("Example", ”Upgrading DB; dropping/recreating tables.")database.execSQL("DROP TABLE IF EXISTS " + AccountsTable.NAME)onCreate(database) }
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"/>
• Android 6+: permission for “dangerous” actions must be requested at runtime (https://developer.android.com/training/permissions/requesting.html )
36
ListView ofuser’s contacts
Content Provider Example: Java (1)// ContentsFragment.java
public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private ListView mContactsListView;
private static final String[] PROJECTION = { ContactsContract.Contacts._ID,ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY };
private final static String[] FROM_COLUMNS = {ContactsContract.Contacts.DISPLAY_NAME_PRIMARY };
@Overridepublic void onActivityCreated(. . .) { // First, call super.onActivityCreated()
Activity activity = getActivity();if (activity != null) {
mContactsListView = activity.findViewById(R.id.contact_list_view);requestContacts(); } }
private void requestContacts() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!hasReadContactPermission()) {requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
PERMISSION_REQUEST_READ_CONTACTS); }else { showContacts(); } }
else { showContacts(); } }37
Content Provider Example: Java (2)// ContactsFragment.java, continued . . .
private boolean hasReadContactPermission() {Activity activity = getActivity();return activity != null &&
activity.checkSelfPermission(Manifest.permission.READ_CONTACTS) ==PackageManager.PERMISSION_GRANTED; }
@Overridepublic void onRequestPermissionsResult(. . . ) {
if (requestCode == PERMISSION_REQUEST_READ_CONTACTS) {if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { showContacts(); }else { /* Show error message */} }
}
private void showContacts() {
// Gets a CursorAdaptermCursorAdapter = new SimpleCursorAdapter(getActivity(), R.layout.list_item_contact,
null, FROM_COLUMNS, TO_IDS, 0);// Sets the adapter for the ListViewmContactsListView.setAdapter(mCursorAdapter);// Initializes the loader, which loads the contacts asynchronouslygetLoaderManager().initLoader(0, null, this);
}
38
Content Provider Example: Java (3)// ContactsFragment.java, continued . . .
@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), ContactsContract.Contacts.CONTENT_URI,PROJECTION, null, null, ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC” );
}
@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Put the result Cursor in the adapter for the ListViewmCursorAdapter.swapCursor(data);
}
@Overridepublic 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: ListViewcompanion object { . . .
private val PROJECTION = arrayOf(ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)private val PERMISSION_REQUEST_READ_CONTACTS = 1private val FROM_COLUMNS = arrayOf(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)private val TO_IDS = intArrayOf(R.id.contact_info) }
private lateinit var mCursorAdapter: SimpleCursorAdapter
override fun onActivityCreated(savedInstanceState: Bundle?) {// First, call super...mContactsListView = activity!!.findViewById(R.id.contact_list_view)requestContacts()}
private fun requestContacts() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!hasReadContactPermission()) { requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS), PERMISSION_REQUEST_READ_CONTACTS) }
else { showContacts() }} else { showContacts() }
}40
Content Provider Example: Kotlin (2)// ContactsFragment.kt, continued . . .
private fun hasReadContactPermission(): Boolean { return activity?.checkSelfPermission(Manifest.permission.READ_CONTACTS) ==
PackageManager.PERMISSION_GRANTED }
override fun onRequestPermissionsResult( . . . ) {if (requestCode == PERMISSION_REQUEST_READ_CONTACTS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {showContacts()}}else { /* Show error message */ }
}
private fun showContacts() {// Gets a CursorAdaptermCursorAdapter = SimpleCursorAdapter(activity, R.layout.list_item_contact,
null, FROM_COLUMNS, TO_IDS, 0)// Sets the adapter for the ListViewmContactsListView.adapter = mCursorAdapter// Initializes the loaderloaderManager.initLoader(0, null, this)
}41
Content Provider Example: Kotlin (3)// ContactsFragment.kt, continued . . .
override fun onCreateLoader(id: Int, args: Bundle): Loader<Cursor> {return CursorLoader(activity, ContactsContract.Contacts.CONTENT_URI,
PROJECTION, null, null, ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC")
}
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {// Put the result Cursor in the adapter for the ListViewmCursorAdapter.swapCursor(data)
}
override fun onLoaderReset(loader: Loader<Cursor>) {mCursorAdapter.swapCursor(null)
}
} // End of class definition
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
Top Related