Khalil Gibran خليل جبران
Khalil Gibran خليل جبران
Your children are not your children,
They are the sons and daughters of life´s longing for itself.
They come through you but not from you.
And though they are with you, they belong not to you.
You may give them your love but not your thoughts,
For they have their own thoughts.
You may house their bdoies but not their souls,
For their souls dwell in the house of tomorrow,
Which you can not visit, not even in your dreams.
You may strive to be like them, but seek not to make them like you,
For life goes not backwards nor tarries with yesterday.
You are the bows from which your children as living arrows are sent forth.
The arhcer sees the mark uopn the ptah of the infniite.
And He bends you with His might that His arrows may go swift and far.
Let your bending in the archer's hadns be for happiness;
For even as He loves the arrow that flies,
So He loves the bow that is stable.
Khalil Gibran خليل جبران
Yuor chidrlen are not yuor chlirden.
Tehy are the snos and duahgetrs of Lfie´s lonigng for istlef.
Tehy cmoe through you but not form you.
And thuogh tehy are wtih you, tehy beolng not to you.
You may give tehm yuor lvoe but not yuor thuohgts.
For tehy hvae their own thuohgts.
You may huose their bdoies but not their suols,
For their suols dewll in the huose of toomrorw,
Whcih you canont viist, not eevn in your draems.
You may stirve to be like tehm, but seek not to mkae tehm lkie you.
For lfie geos not bacwkard nor tarries with yesetrday.
You are the bows from whcih your chidlren as liivng arorws are snet fotrh.
The arhcer sees the mark uopn the ptah of the infniite.
And He bedns you with His mihgt that His arorws may go swfit and far.
Let your benidng in the arcehr's hadns be for hapipness;
For eevn as He loevs the arorw that fleis,
So He loevs the bow taht is stbale.
The following exercises in HTML, CSS and JavaScript are to be hosted in a small website, web-projects
to be submitted for marking. They also form the basis of the lab test with multiple choice questions.
Start by creating the initial directory structure for web-projects
web-projects/test-site
Objective: Start a project with what will your website look like? and continue with using a naming conventions, and creating files and folders for a typical project and finally follow the instructions structuring the content with HTML and styling the content with CSS
Open the index.html
file for the project in firefox and follow the instruction up to The JavaScript debuggger in What are browser developer tools? to examine the HTML and CSS code.
web-projects/first-form
Create a file first-form.html and follow the execise to complete the form. Validate the code and Check it in Firefox.
web-projects/structure-form
Note, the Active learning section, the exercise starts with a minimal payments-form.html and a payments-form.css files. Code is then incrementally added to complete the form and the style declarations.
Objective: To understand how to structure HTML forms and give them semantics so they are usable and accessible.
web-projects/html5-form
Create a file, html5-form.html and copy and paste a basic form from the previous exercise. This exercise highlights the new input types that may not be implemented in some browsers. Add the new input types to the form. Save the file as html5-form.html and try the form in both Firefox and Chrome.
web-projects/school
Note, the starting point for this exercise is an index.html and a styles.css file.
Objective: To test comprehension of CSS text styling techniques.
web-projects/accessibility
Note, the starting point for this exercise is a zip file download with all the necessary files to start the exercise. Download and uncompress the zip file in the accessibilty folder.
Objective: To test basic knowledge of accessibility fundamentals.
web-projects/layout
Task general layout
A responsive layout with 1 to 3 fluid columns using grid-template-areas section has the code for a classic website layout. The task is to:
HTML
and the CSS
code and delete the Advertisind element.JavaScript is more difficult to learn than related technologies such as HTML and CSS. Before attempting to complete the following JavaScript exercises, you are strongly advised to get familiar with HTML and CSS first.
web-projects/guessing-game
Objective: Demonstrate understanding of introductory JavaScript, and what writing a JavaScript program involves by completing the guessing game example.
web-projects/debug-js
Objective: Troubleshooting javaScript code. Complete the guessing game with errors example.
variables
Objective: Test your skills: variables. See the Variables article for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
operators
Objective: Test your skills: Math. See the Basic math in JavaScript ‐ numbers and operators article for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
strings
Objective: To understand that strings are objects, and learn how to use some of the basic methods available on those objects to manipulate strings. Test your skills: Strings. See Handling text — strings in JavaScript and Useful string methods for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
arrays
Objective: Test your skills: Arrays. See the Arrays article for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
web-projects/silly-story
conditional
Objective: Test your skills: Conditionals. See the Making decisions in your code — conditionals for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
loops
Objective: Test your skills: Loops. See the Looping code for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
functions
Objective: Test your skills: Functions. See the Functions — reusable blocks of code for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
events
Objective: Test your skills: Events. See the Introduction to events for answers to the tasks.
Consider the marking guide and reflect on your understanding of the code.
web-projects/image-gallery
Objective: To test comprehension of JavaScript loops, functions, conditionals, and events.
Complete the Image gallery exercise.
web-projects/dom-api
web-projects/final-js
Hint! use the developer tool to examine the code for this page.
The second assessment is in two parts. The Lab Exercises and a project to create a Website for a topic of your own choosing. In preparation for submission, please complete the following:
<!DOCTYPE html> <html> <head> <title>labs</title> <meta charset="utf-8" /> <meta name="author" content="insert your name here" /> <meta name="description" content="lab exercises and website" /> </head> <body> <main> <section id="bio"> <h1> Website Development </h1> <h2> Your full name </h2> <p> <strong>ID: <em>insert your id</em></strong>, <strong>Group: <em>insert your group</em></strong> </p> </section> <section id="exercises"> <h2>Structuring the web (HTML) and Styling the web (CSS) exercises</h2> <ol> <li> <a href="test-site/index.html"> test site</a> </li> <li> <a href="first-form/first-form.html"> first form</a> </li> <li> <a href="structure-form/payments-form.html"> structure form</a> </li> <li> <a href="html5-form/html5-form.html"> html5 form</a> </li> <li> <a href="school/index.html"> school</a> </li> <li> <a href="accessibility/index.html"> accessibility</a> </li> <li> <a href="layout/layout-general.html"> layout</a> </li> </ol> <h2>JavaScript exercises</h2> <ol> <li> <a href="guessing-game/number-guessing-game-start.html"> guessing game</a> </li> <li> <a href="debug-js/number-game-errors.html"> debug JavaScript</a> </li> <li> <a href="silly-story/index.html"> silly story generator</a> </li> <li> <a href="image-gallery/index.html"> image gallery</a> </li> <li> <a href="dom-api/dom-example.html"> dom api</a> </li> <li> <a href="for you to complete the URL"> final JavaScript</a> </li> </ol> <h2>Website</h2> <ol> <li> <a href="website/page-1.html">Page 1</a> </li> <li> <a href="website/page-2.html">Page 2</a> </li> <li> <a href="website/page-3.html">Page 3</a> </li> <li> <a href="website/page-4.html">Page 4</a> </li> <li> <a href="website/page-5.html">Page 5</a> </li> </ol> </section> </main> </body> </html>
For the lab exercises:
wd.html
file and note the hyperlinks use relative pathnames to the directory structure used in the exercises. Check each links loads the correct exercise
For the Website:
grid
and flexbox
layouts.
html
form elements
Read: HTML basics.
HTML
is a document oriented specification<html>
<head>
<meta charset="utf-8">
<title>My test page</title>
</head>
<body>
<h1>Hello world!</h1>
⋮
</body>
</html>
Browser converts HTML to Document Object Model(DOM)
var myHeading = document.querySelector('h1');
myHeading.onclick = function() {
alert('Ouch! Stop poking me!');
myHeading.textContent = 'Hello world!';
myHeading.style.color = "red";
}
How many syntactical rules?
html, body, nav, section, article, headings, paragraphs, lists, forms, footers,
etc.
<html>
<head>
<title>My test page</title>
</head>
<body>
⋮
</body>
</html>
<p>
A site where the learned share questions:
<a href="edge.org">edge.org</a>
</p>
/>
.<img src="fun.png" alt="kids playing" />
<message>
<warning>
Hello World
<!--missing </warning> -->
</message>
<message>
<warning>
Hello World
</warning>
</message>
<!ELEMENT message (warning)>
<!ELEMENT warning (#PCDATA)>
jokes, joke, punchline, question, answer, genre
<?xml version="1.0" encoding="UTF-8"?>
<?doctype jokes SYSTEM "jokes.dtd"?>
<jokes>
<joke>
<question>
Why did the chicken cross the road?
</question>
<punchline>
To get to the other side!
</punchline>
</joke>
<joke>
<question>
What did the gold fish say to her mate swimming in a Tank?
</question>
<punchline>
I didn't know you could drive!
</punchline>
</joke>
</jokes>
doctype
and the DTD?<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT jokes (joke+)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT joke (question, punchline)>
<!ELEMENT question (#PCDATA)>
<!ELEMENT punchline (#PCDATA)>
jokes {
font-family: verdana, Courier, mono;
font-size: 48px;
background-color:#CCFFFF;
margin: 40px;
}
joke {
display: block;
background-color:#FFFF99;
border: 2px solid green;
border-radius: 5px;
margin: 1em;
padding: 1em;
}
question {
display: block;
color:#CC0000;
}
punchline {
display: block;
color:#000099;
}
Try: Debugging HTML
doctype
, browsers correct with "quirk mode"
XHTML
is XML well-formed and valid to a DTD
Read: How CSS works.
<p>
Let's use: <span>CSS</span>
</p>
span {
border: 1px solid black;
background-color: red;
}
Let's use: Cascading Style Sheet
<p id="special" class="general">
some content
</p>
p { color: red; }
#special { color: green; }
.general { color: blue; }
class
has higher specificity than element type p
id
has higher specificity than class
/* These styles will style our link in all states */
a {
color: blue;
font-weight: bold;
}
/* declare visited links to be the same
color as non visited links */
a:visited {
color: blue;
}
/* Highlight the link when it is hovered over (mouse over),
activated (mouse down) or focused (keyboard) */
a:hover,
a:active,
a:focus {
color: darkred;
text-decoration: none;
}
/* import another CSS file */
@import "layout.css";
@media (min-width: 801px) {
body {
margin: 0 auto;
width: 800px;
}
}
The CSS box model is the foundation of layout on the Web
Types of CSS boxes (set with disply
property)
For children of the box and complex layout:
<link rel="stylesheet" href="style.css">
<style>
h1 { color: red; }
</style>
<h1 style="color: blue;">Hello World!</h1>
text/html, text/css, text/javascript
image/jpeg, image/png, image/svg+xml
audio/mpeg, audio/vorbis
font/woff, font/ttf
video/mp4, video/ogg
multipart/form-data
bits
! Text, Image, Audio, Video
audio
and video
elements<audio controls>
<source src="bethoven.mp3" />
<!-- more sources with differnet formats … -->
Your browser does not support the audio element.
</audio>
<video controls="controls">
<source src="earth.ogv" />
<!-- more sources with differnet formats … -->
Your browser does not support the video element.
</video>
<p>CSS is declarative</p>
p { background-color: red; }
CSS is declarative
const myParagraph = document.querySelector("p");
myParagraph.style.color = "black";
myParagraph.style.backgoroundColor = "red";
The internet is a world-wide network of computers that makes the Web possible. Read: How the internet works?
Finding Computers
The aim is to have an overview understanding of Hypertext Transfer Protocol (HTTP). How does HTTP allows for surfing the web? and what are the common security issues on the web?
Create an HTML file and markup the following questions (and answers). Save the file as http_exercise.html
The answers are in the relevant articles in the HTTP tutorials.
get
and post
request.
Read: An overview of HTTP.
text/html, text/css, text/javascript
image/jpeg, image/png, image/svg+xml
audio/mpeg, audio/vorbis
font/woff, font/ttf
video/mp4, video/ogg
multipart/form-data
The biggest library humanity has ever created, accessible to a paragraph amongst the trillions of documents growing by the second!
can you explain static types and methods?
if (true) {
Watch Java Memory Management, video from Virtual Pair Programmers
continue with the lab exercises
} else {
Read Introduction to Programming in Java ‐ MIT open courseware
Watch Java Memory Management, video from Virtual Pair Programmers
Dedicate extra time for programming practice
}
"The UI consists of a hierarchy of objects called views — every element of the screen is a View. The View class represents the basic building block for all UI components, and the base class for classes that provide interactive UI components such as buttons, checkboxes, and text entry fields". Layouts and resources for the UI
The Activity
class is the major building block of the user interface (UI) and Intent
is the communication object between activities. Read the related concepts and complete the following exercises.
putExtra
and getExtra
methods for adding and getting data from the intent
object.
startActivityForResult(intent, TEXT_REQUEST);
and onActivityResult(int requestCode, int resultCode, Intent data)
are key methods for communicating between the two activities
Activities and intents are key concepts in Android, read the related concepts before moving onto the next exercise
"The activity lifecycle is the set of states an activity can be in during its entire lifetime, from the time it's created to when it's destroyed and the system reclaims its resources. As the user interacts with your app and other apps on the device, activities move into different states."
This has many implications for the design of the application. Read the related concepts and complete the following exercises.
See the code example: https://github.com/ebbi/TaskIntent
Using Android Studio, create a new project, TaskIntent
.
When prompted for the Minimum API Level, select: API 29: Android 10 (Q).
AndroidManifest.xml
file
Run
your app on a virtual device using API 29
intent
branch
TaskRepository
and Task
classes
MainActivity
to TaskActivity
, and similarly activity_main.xml
to activity_task.xml
TaskActivity
to include the TaskRepository
and the Next
listener button
Run
the app and notice the task resets to the first task when you rotate the phone. Fix the "rotation problem" (see previous exercise)
NEXT
button, implement a PREV
button
See the code example for and Activity
with an Intent
at: https://github.com/ebbi/todo-detail-app
NEXT
and PREV
todo tasks.
Using Android Studio, create a new project, TaskFragment
.
When prompted for the Minimum API Level, select: API 29: Android 10 (Q).
AndroidManifest.xml
file
Run
your app on a virtual device, Pixel 3 API 29
fragment
branch
TaskRepository
and Task
classes
MainViewModel
class used for communicating data between fragments
TaskFragment
and the TaskDetailFragment
fragments to be dynamically loaded into the parent MainActivity
view container. A Single Page Application (SPA) pattern.
Run
the app and notice the task does not resets to the first task when you rotate the phone. Why?
NEW
button to create new tasks
EDIT
button to update existing tasks (refactor Detail fragment and change it to edit)
Activity
and ActivityManager
classes
Hello world
ActivityManager
in the OS is in charge, not You!
Activity
class is the entry point for UI
main
thread?
hello world
manifest
file<activity android:name=".HelloWorld">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
HelloWorldActivity
public class HelloWorldActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
/* call the super class onCreate to complete
the creation of Activity, such as the view hierarchy */
super.onCreate(savedInstanceState);
/* set the user interface layout for this Activity */
setContentView(R.layout.activity_helloWorld);
}
}
view
definition<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
…
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HelloWorldActivity">
<TextView
android:id="@+id/textViewMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_text"
app:layout_constraintBottom_toBottomOf="parent"
… />
</android.support.constraint.ConstraintLayout>
and a file strings.xml in the res folder defines:
<resources>
<string name="message_text">Hello World</string>
…
<resources>
Hello World
walk-through?
@override
and callback
function?
Activity
lifecycle related to callback
functions? Hello World
only used one onCreate
callback function. See the Task Activity
with rotation event as an example of other lifecycle events.
interface
in the View
class that contains a single callback method
View.OnClickListener onClick()
View.OnLongClickListener onLongClick()
View.OnFocusChangeListener OnFocusChangeListener
View.OnKeyListener OnKeyListener()
View.OnTouchListener OnTouchListener()
View.OnCreateContextMenuListener OnCreateContextMenuListener()
View.onClickListener
fires an event that results in the corresponding event handler, onClick
call back method being called.
private
data conveniently available to anonymous inner class
listener
for each view
object
buttonNext.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
/* do something when the button is clicked */
}
});
interface
in the class definitionpublic class ActivityMain extends Activity
implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedValues) {
…
Button button = (Button)findViewById(R.id.next);
button.setOnClickListener(this);
}
@Override
public void onClick(View view) {
/* do something when the button is clicked */
{
…
}
interface
for any view objects/* Create an anonymous implementation of OnClickListener
for all clickable view objects */
private View.OnClickListener mTodoListener =
new View.OnClickListener() {
public void onClick(View v) {
/* get the clicked object and do something */
switch (v.getId() {
case R.id.checkBoxIsComplete:
default:
break;
}
}
};
And the usage would be:
CheckBox checkboxIsComplete =
(CheckBox)findViewById(R.id.checkBoxIsComplete);
checkboxIsComplete.setOnClickListener(mTodoListener);
/* listener implementation with the method name
defined in the view definition */
android:onClick = "onCheckboxIsCompleteClick"
Activity Lifecycle
, persistence through state changes
Activity Lifecycle
Activity
has four statesactive
(or running at the top of the activity stack)
paused
— alive in memeory, visible, lost focus
stopped
— obscured by another activity, alive in memory, not visible
paused
or stopped
— may lead to destroyed
and restarted
to previous state
Activity
‐ three key loopsonCreate
to onDestroy
onStart
to onStop
onResume
to onPause
Lifecycle
and saving persistent stateFile
, Database)
Bundle
key, value pairs
@Override
protected void onCreate(Bundle savedInstanceState) {
…
/* Restore the state */
if (savedInstanceState != null) {
mCount = savedInstanceState.getInt("count");
mShowCount.setText(String.valueOf(mCount));
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("count", mCount);
}
Lifecycle
class and observer patternevents
— enumeration from framework and lifecycle
class
lifecycle
class events map to callback
methods
lifecycle
object
LifecycleObserver
allows for lifecycle-aware components (observer pattern to monitor lifecycle status)
Activity
and Intent
Activity
and Intent
Activity
and Intent
Activity
— a single screen in an app
Intent
— a message object to start an activity
Activity
and Intent
communicate via the OS ActivityManager
Activity
in one App to use Activity
in another App
Intent
Intent
— messaging object to request an action from another app component
Intent
— a passive data structure that holds an abstract description of an operation to be performed
Intent
constructorsIntent()
Intent(String action)
Intent( String action, URI uri )
Intent( Context context, Class<?>cls )
Intent( String action, Context packageContext, Class<?>cls )
Intent
examplessetAction(), putExtra, addCategory(), …
Intent
— action is defined, target component is unkownIntent intent = new Intent();
intent.setAction( Intent.ACTION_WEB_SEARCH );
intent.putExtra( SearchManager.QUERY,"search text" );
Intent browserIntent = new Intent(
Intent.ACTION_VIEW,
Uri.parse( "http://www.ecosia.com" )
);
browserIntent.addCategory( CATEGORY_BROWSABLE );
Intent
— target component is knownIntent intent = new Intent(
packageContext, ExampleActivity.class
);
intent.putExtra( "EX1","example value" );
intent.putExtra( "EX2","example value" );
late runtime binding between the code in different applications
intent
be instantiated?"from each according to their ability, to each according to their need"
Activity
has the Intent
data
Activity
uses the Intent
data
static
method in called Activity
public static Intent newIntent(
Context packageContext, int todoIndex){
Intent intent = new Intent(
packageContext, TodoDetailActivity.class);
intent.putExtra(TODO_INDEX,todoIndex);
return intent;
}
The calling activity:
Intent intent =
TodoDetailActivity.newIntent(TodoActivity.this, mTodoIndex);
Intent
result backstartActivityForResult(Intent intent, int requestCode);
@Override
public void onClick(View v) {
Intent intent = TodoDetailActivity.newIntent(
TodoActivity.this, mTodoIndex);
// requestCode = int constant for a single activity
startActivityForResult(intent, IS_SUCCESS);
}
Activity
implement two method for setting results
setResult(int resultCode);
setResult(int resultCode, Intent intent);
resultCode
set to,Activity.RESULT_OK
orActivity.RESULT_CANCELED
extras
in intent
for more data
Intent intent = new Intent();
intent.putExtra(IS_TODO_COMPLETE, isChecked);
setResult(RESULT_OK, intent);
Callback onActivityResult()
to retrieve any set result.
@Override
protected void onActivityResult(
int requestCode, int resultCode, Intent intent) {
if (intent != null) {
// data in intent from child activity
boolean isTodoComplete =
intent.getBooleanExtra(IS_TODO_COMPLETE, false);
…
}
}
Fragments
Activity
and View
Fragment
represents a behaviour or a portion of user interface, has its own lifecycle, receives its own input events, and can be added or removed while the activity is running
Fragment
can be dynamically loaded allowing for flexible UI
FragmentManager
View
decoupled by delegating UI to Fragment
Activity
uses placeholder views for fragments
Activity
uses a FragmentManager
to dynamically add and remove Fragments
from Views
in a fragmentManager transaction
.
FragmentManager
transactions can change fragment combinations for different screen sizes
fragment
lives in a ViewGroup inside the activity's view hierarchy and affected by the Activity
lifecycle
Fragment
LifecycleonCreate()
initialize components of the fragment to retain when the fragment is paused or stopped, then resumed
onCreateView()
called for fragment to draw its user interface and return a view
onPause()
first indication user is leaving the fragment; persist session data
startActivityForResult()
is Fragment.startActivityForResult()
Activity.onActivityResult()
overide Fragment.Activity.onActivityResult()
Fragment
setResult, only Activity has Activity.setResult()
getActivity
method to access the parent Activity's intent data directly leads to coupling. getActivity().setResult(Activity.RESULT_OK, null);
Activity
Static method to bundle any arguments and return the fragmentgetActivity
method to access the parent Activity's intent data directly leads to coupling
fragment
leads to decoupling as any activity can pass its data and receive the fragment
In TodoFragment
public static TodoFragment newInstance(int todoId) {
Bundle args = new Bundle();
args.putSerializable(ARG_TODO_ID, todoId);
TodoFragment fragment = new TodoFragment();
fragment.setArguments(args);
return fragment;
}
/*
Any Activity (such as TodoActivity) can
call the static method and pass it's intent data
to be bundles and recieve the decoupled fragment
*/
protected Fragment createFragment(){
int todoId = getIntent()
.getSerializableExtra(EXTRA_TODO_ID);
return TodoFragment.newInstance(todoId);
}
The same pattern was applied to decouple
intents with data being passed and a static
method returns the intent.
In TodoListFragment
public void onClick(View view) {
Intent intent = TodoActivity
.newIntent(getActivity(), mTodo.getId());
startActivity(intent);
}
and in TodoActivity:
public static Intent newIntent(
Context packageContext, UUID todoId) {
Intent intent = new Intent(packageContext,
TodoActivity.class);
intent.putExtra(EXTRA_TODO_ID, todoId);
return intent;
}
ViewModel
class for UI-related data
onSaveInstanceState
for simple dataViewModel
is lifecycle awareActivity
and Fragment
react to events and ViewModel
Asynchronous loading dataViewModel
objects scoped to the lifecycle
passed to the ViewModelProvider
activity
finishes or fragment
detachedSharedViewModel model =
ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
Single Page Application (SPA) lab exercise: https://github.com/ebbi/TaskFragment.git
Room, LiveData, and ViewModel and lab exercise: Android Room with a View and https://github.com/ebbi/TodoMVVM
Observable
(Java 9 deprecated Observable)Room uses RxJava for data observation
Room uses annotations with three main components
The code for this exercise can be found at: https://github.com/ebbi/todo-first-app.git
First todo app : MVC, Manifest, Activity, onCreate, View Objects, lifecycle, Listeners, Anonymous functions, event handlers, callbacks, Bundle, Resources
Model
objects, the M in MVC, implement the application logic and the data the logic is applied to. The Todo
app should implement the logic for creating and updating todo lists. For simplicity, in this implementation the data is a simple static-array
defined as a resource; in later exercises, the model logic and data is abstracted into its own model classes.
Android View
objects, the V in MVC, know how to draw themselves on the screen and respond to user input. The layout and the View objects hierarchy are defined in XML and are inflated into View
objects as part of a controller class initialisation.
Controller objects, the C in MVC, (typically, Activity
classes in Android), connect the view and the model objects together by responding to events triggered by View objects and manage the flow of data between the model and the view. A click
on a Button
View object is a common example of an event handled by a controller class.
Android framework lends itself to the MVC architecture and it follows naturally particularly with the view objects and layout being abstracted and separately defined in XML and the Activity
classes controlling the User Interaction(UI) on the view objects. The separation of the Model and the Controller has to follow good programming practice. A controller as its name suggest should only have control logic and delegate all else to model classes.
In Android Studio: Start a new Android Studio project Application Name:Android_todo_first
Company Domain:example.com
(check theproject name
is at the end of the project location pathname.) Next select the option, Phone and Tablet and from the drop-down list, select: API 27: Android 8.1 (Oreo) Next Select Empty Activity Next change the Activity Name toTodoActivity
, keep the defaults Finish Android Studio creates all the necessary files and opens the IDE. Run > Run App Setup or use an existing virtual device, choose Nexus 5X API 27 targeted for Android 8.1 (Google Play) (installing the virtual device may take some time.) Once Android is running, the default 'Hello World!' message is displayed.
This first Todo
prototype cycles through a list of Todos without any further complexity or persistent storage. As such all that is needed is a todos
array which can be stored as a string-array
resource.
todos
string array as a resource
Open the res/values/string.xml
file and replace the resources
XML element with the following:
<resources>
<string name="app_name">Android_todo_first</string>
<string-array name="todos">
<item>Wake up</item>
<item>Drink Coffee</item>
<item>Make at least one person laugh</item>
<item>Plant a tree</item>
<item>Ponder on duality of existance</item>
<item>Go to sleep</item>
</string-array>
</resources>
As part of the build process, the SDK tools generate symbols for each resource, which you can use in your application code to access the resources.
Resources are the additional files and static content that your code uses, such as bitmaps, layout definitions, user interface strings, animation instructions, and more. See: App resources overview
Symbols, app_name
and todos
can now be used as global variables!
The Android View
class represents the basic building block for user interface components. Android provides a set of User Interactive components (button, text fields, etc.) referred to as widgets. View
is the base class for widgets. The View
widgets can be grouped together into invisible container classes known as layouts
.
Android also provides other UI modules for special interfaces such as dialogs, notifications, and menus.
. To get started, read layouts and review Layouts and resources for the UI
Todo
View
The Todo
view definition below is an XML definition of a ConstraintLayout
container that contains the definition of a TextView
widget intended to hold the todo
text and two Button
widgets for next and prev cycling through the todos
.
Open the res/layout/activity_todo.xml
file and replace the content with the following view definition.
Todo
view definition<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TodoActivity">
<TextView
android:id="@+id/textViewTodo"
android:layout_width="330dp"
android:layout_height="336dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:text="@string/todos"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.515"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.502"
android:layout_marginBottom="16dp"
tools:layout_editor_absoluteY="76dp"/>
<Button
android:id="@+id/buttonPrev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="176dp"
android:text="@string/prev"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/buttonNext"/>
<Button
android:id="@+id/buttonNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
Note, the errors, Cannot resolve symbol
for resource names.
To define these resources (constant strings!), open the res/values/string.xml
file and insert the following after the app_name
definition.
<string name="todos">Todos</string>
<string name="next">Next</string>
<string name="prev">Prev</string>
Run the app to see the view.
Android Activity
is the controller class for any view and it's user interaction. For a brief overview, read the first section on activities.
The TodoActivity
class has the default skeleton of an activity controller
class with the default onCreate
method override.
The first call in onCreate
is to the parent onCreate
to complete task such as building the View hierarchy. And second, a call to setContentView(R.layout.activity_todo)
. If this is not clear, please read the first section on activities.
Run the app and remind yourself of the view. The User interaction to be coded is with the click events on the prev
and next
view buttons.
A click event on an object requires a click listener
and a corresponding handler method to respond to the click event. The following implementation reuses the view object setOnClickListener
and an anonymous function to handle the event.
Handling the click event is to override the onClick
event with code to update the textView
object with the current todo
data. Before this, the todo
data has to be retrieved from the string-array
.
Overall logic:
TodoTextView
object for displaying todos
mTodos
, from res/values/strings.xml
mTodos
array in the TodoTextView
buttonNext
to override its setOnClickListener
View.OnClicklistener
function as a handler for the buttonNext.onClickListener
.
onClick
method in the anonymous function to cycle the mTodo
array (checking for the end of array) and update the TodoTextView
with the current todo.
TodoActivity
codepublic class TodoActivity extends AppCompatActivity {
private String[] mTodos;
private int mTodoIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
/* call the super class onCreate to complete the creation
of activity with state changes */
super.onCreate(savedInstanceState);
// set the user interface layout for this Activity
setContentView(R.layout.activity_todo);
// initialize member TextView so we can manipulate it later
final TextView TodoTextView;
TodoTextView = (TextView) findViewById(R.id.textViewTodo);
// read the todo array from res/values/strings.xml
Resources res = getResources();
mTodos = res.getStringArray(R.array.todos);
// display the first task from mTodo array in the TodoTextView
TodoTextView.setText(mTodos[mTodoIndex]);
Button buttonNext;
buttonNext = (Button) findViewById(R.id.buttonNext);
// OnClick listener for the Next button
buttonNext.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
mTodoIndexx += 1;
TodoTextView.setText(mTodos[mTodoIndex]);
}
});
}
}
TodoActivity
class with the code above
Cannot resolve symbol
.
Next
button crashes on the last item in the Todos
array.
Compile time errors are generally to do with syntactical rules and relatively easy to correct. Read the error message (at least twice!) and if it is not clear, search the error message in known sites such as Android Developer API Guides (note, the site has a powerful search) and Stack overflow.
Run time errors are due to inconsistency in the program logic or algorithm. For example, the TodoActivity
runs fine until the index is incremented beyond the last element of the todos
array.
If the run time error message was not immediately clear then the next step is to see the stack trace in the Android Monitor tab (bottom tool bar). There is generally a link with the class name and line number that you could click. This is the last statement that could not be executed.
If examining the stack trace did not resolve the run time error; it is useful to set a debugging break point at the line number the execution stopped.
mTodoIndex
values until it crashes.
TodoActivity
error ArrayIndexOutOfBoundsExceptionThere is a failure in the logic in that the index for the array is incremented without checking for the end of the array. This leads to an attempt to access a non-existent element of the array, hence, ArrayIndexOutOfBoundsException
Correct code that does check for the size of the array:
mTodoIndex = (mTodoIndex + 1) % todos.length;
(Note, % in Java returns the remainder, hence the index will never exceed the array size an alternative less efficient solution would be to test for the size of the array).
To see the rotation problem:
Every instance of an Activity
has a life cycle and transitions between 4 states namely, resumed, paused, stopped and nonexistent with corresponding methods: onCreate, onDestroy, onStart, onStop, onResume
and onPause
. These methods are called life cycle callbacks. We override these callbacks and Android calls the life cycle callbacks at the appropriate time such as after rotating the phone.
Read this overview of Activity lifecycle and state.
The solution to rotation problem is to store the TODO_INDEX
across rotation state changes. On rotating the phone, Android calls the Activity
's callback method onSaveInstanceState(Bundle)
. This method can be overridden and include code to store the TODO_INDEX
.
/* In case of state change, such as rotating the phone,
store the mTodoIndex */
/* override to write the value of mTodoIndex into
the Bundle with TODO_INDEX as its key */
private static final String TODO_INDEX = "todoIndex";
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt(TODO_INDEX, mTodoIndex);
}
Once the phone is rotated, Android calls the Activity
's onCreate(Bundle savedInstanceState)
callback method. Note, the Bundle
object savedInstanceState
has the TODO_INDEX, mTodoIndex)
key, value pair. The index can be retrieved in the onCreate
callback method and the correct todo
displayed.
/* check for saved state due to changes such as rotation
and restore any saved state such as the TODO_INDEX */
if (savedInstanceState != null){
mTodoIndex = savedInstanceState.getInt(TODO_INDEX, 0);
}
Run the app, press Next, rotate the phone and the same todo
should display, whereas previously it reset to the first todo
.
Android detects the device configuration change and looks for resources that better match the changed configuration. For views, Android uses a configuration qualifier namely, using -land
suffix in the directory name.
Try the following, to create a new landscape todo
view.
app/src/main/res
res
folder and create a new Directory named, layout-land
layout-land
directory and create a new file named, activity_todo.xml
(the same filename as the portrait view definition)
activity_todo.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TodoActivity">
<TextView
android:id="@+id/textViewTodo"
android:layout_width="330dp"
android:layout_height="336dp"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:text="@string/todos"
android:textColor="@android:color/holo_green_dark"
android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.515"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.502"
tools:layout_editor_absoluteY="76dp"/>
<Button
android:id="@+id/buttonPrev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="176dp"
android:text="@string/prev"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.23"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/buttonNext"/>
<Button
android:id="@+id/buttonNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginRight="16dp"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
TodoActivity
Next
button crashing at the end of the Todos
array. (see the Debug section)
Next
, add the code for the Prev
button.
: MVC, Manifest, Activity, onCreate, View Objects, lifecycle, Listeners, Anonymous functions, event handlers, callbacks, Bundle, Resources
manifest
main activity
, walk-through the code showing your understanding of the design and the implementation. How many steps and questions can you write down from the start in manifest
to the view being displayed?
onCreate
callback method
/* call the super class onCreate to complete the creation
of activity with state changes */
super.onCreate(savedInstanceState);
/* set the user interface layout for this Activity */
setContentView(R.layout.activity_todo);
R
?savedInstanceState
and Bundle
?activity_todo
?event
and a similar walk-through the event
handler
The code for this exercise can be found at: https://github.com/ebbi/todo-detail-app.git
Todo detail app : Intent, Extra, Communicating between activities, Static method, MVC, Activity, Intent, View Objects, Listeners, Anonymous functions, event handlers, callbacks, lifecycle, state instances, Bundle
The MVC implementation of a todo list of todos each with a todo detail. In terms of a class diagram, this could be a todolist
activity class associated with a todoDetail
Activity class. To create these classes an intent
communication object is passed from the todoList
activity to the ActivityManger
in Android which returns the intended todoDetail
object. This is after a user event selecting a todo for its details. Consider the following sequence diagram and follow the steps to create a todoDetailApp
.
In Android Studio: Start a new Android Studio project Application Name:todo-detail-app
Company Domain:example.com
(check theproject name
is at the end of the project location pathname.) Next select the option, Phone and Tablet and from the drop-down list, select: API 27: Android 8.1 (Oreo) Next Select Empty Activity Next change the Activity Name toTodoActivity
, keep the defaults Finish Android Studio creates all the necessary files and opens the IDE. Run > Run App Setup or use an existing virtual device, choose Nexus 5X API 27 targeted for Android 8.1 (Google Play) (installing the virtual device may take some time.) Once Android is running, the default 'Hello World!' message is displayed.
A very basic model for testing is an array of todos
with a corresponding array of todo_detail
.
Open the res/values/string.xml
file and replace the resources
XML element with the corresponding content from the git repository.
As part of the build process, the SDK tools generate symbols for each resource, which you can use in your application code to access the resources.
Resources are the additional files and static content that your code uses, such as bitmaps, layout definitions, user interface strings, animation instructions, and more. See: App resources overview
Symbols, app_name
and todos
can now be used as global variables!
The Android View
class represents the basic building block for user interface components. Android provides a set of User Interactive components (button, text fields, etc.) referred to as widgets. View
is the base class for widgets. The View
widgets can be grouped together into invisible container classes known as layouts
.
Android also provides other UI modules for special interfaces such as dialogs, notifications, and menus.
. To get started, read layouts and review Layouts and resources for the UI
TodoDetail
View
The TodoDetail
view definitions have the ConstraintLayout
container that contains the definition of the remainning widgets intended to hold the todos and their details.
Replace the res/layout/
files with the corresponding content from the git repository
Note, the errors, Cannot resolve symbol
for resource names.
To define these resources, replace the res/values/string.xml
file with the corresponding content from the git repository
Run the app to see the view.
TodoActivity
controller has a Todo details button click event and associated onClick handler.
onclick
handler calls a static
method in the TodoDetail
controller, passing it the id of the todo and receiving the intent object.
Intent intent = TodoDetailActivity.newIntent(
TodoActivity.this, mTodoIndex);
startActivityForResult(intent, IS_SUCCESS);
starts the TodoDetailActivity
and any result can be accessed in the TodoActivity
by overriding the onActivityResult
method
TodoActivity
and TodoDetailActivity
classes.
Next
, add the code for the Prev
button.
: Intent, Extra, Communicating between activities, Static method
manifest
main activity
, walk-through the code showing your understanding of the design and the implementation of the todo detail use case. How many steps and questions can you write down from the start in manifest
to the view being displayed?
onCreate
callback method
detail
click event.
onCick
event handler
buttonTodoDetail.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
/* Note, the child class being called has a static method
determining the parameter to be passed to it in the intent
object */
Intent intent = TodoDetailActivity.newIntent(
TodoActivity.this, mTodoIndex);
/* second param requestCode identifies the call as there
could be many "intents" */
startActivityForResult(intent, IS_SUCCESS);
/* The result will come back through */
onActivityResult(requestCode, resultCode, Intent) method */
}
});
newIntent
?todo
index stored?event
and a similar walk-through the event
handler in the detail
view checkbox for completed todos
todo
(similar to checkbox for completed todo)
onActivityResult
The code for this exercise can be found at: https://github.com/ebbi/todo-fragment-app.git
Todo fragment app : MVC, FragmentManager, FragmentTransaction, Fragment, Inflater, View Objects, callbacks
Whilst the Android XML view definition and view objects provide for seperation of the V in MVC, the view remains tightly coupled with the Activity class. This is problematic in principle. The V in MVC needs to be more decoupled and further abstracted so that it could be composed and recomposed as necessary at run time.
It is true that an Activities view may change at run time but the code for the change is inside the activity, hence the tight coupling. To decouple is to abstract the view control code out of the Activity and delegate it to another class. This is achieved in Android with fragments.
A fragment is a controller object that an activity can delegate view management tasks to. The Activity's own view can have a placeholder(s) defined to insert any framgent(s) view. This decoupling allows for views to be dynamically recomposed as the result of device or user requirements and events.
Note in the following class diagram for an example todo fragmnet app:
In Android Studio: Start a new Android Studio project Application Name:todo-fragment-app
Company Domain:example.com
(check theproject name
is at the end of the project location pathname.) Next select the option, Phone and Tablet and from the drop-down list, select: API 27: Android 8.1 (Oreo) Next Select Empty Activity Next change the Activity Name toTodoActivity
, keep the defaults Finish Android Studio creates all the necessary files and opens the IDE. Run > Run App Setup or use an existing virtual device, choose Nexus 5X API 27 targeted for Android 8.1 (Google Play) (installing the virtual device may take some time.) Once Android is running, the default 'Hello World!' message is displayed.
support-v4
which includes fragment support android.support.v4.app.Fragment
support-v7, appcompat-v7, recyclerview-v7
and many moreThe following are brief highlights of the important points (the code can be found at: https://github.com/ebbi/todo-fragment-app.git)
Being a controller class, the TodoFragment sits between the model and the view and supports the getter and setter methods for the data in the view.
The model is currently a Plane Old Java Object(POJO) with the getter and setter methods for the data that represents a Todo. See the Todo.java class.
The data source is abstracted to TodoDS java class. Note, the class has:
private
constructor
public static get
method to return the same TodoModel object instance (simple implementation of Singleton pattern)
Consider the Class diagram
onCreate
has a FragmentManager
FragmentManager
dynamically loads the fragment views within a fragment transaction
onCreateView
uses the inflator
class to create the view objects from the XML definitions
manifest
main activity
, walk-through the code showing your understanding of the design and the implementation of the todo fragment. How many steps and questions can you write down from the start in manifest
to the view being displayed?
inflator
do?
onCreate
and onCreateView
?
TodoModel
(currently not used; see the todo-list-app as an example of (non persistant) CRUD operation for creating and updating todos). Implement CRUD operation use cases.
The code for this exercise can be found at: https://github.com/ebbi/todo-component-app
Todo component app : Vertical and horizontal swipes with RecyclerView and ViewPager, Fragments, Toolbar + Create, Read, Update a todo
view
objects to fill a screen
RecyclerView
relies on an Adapter
with a typical sequence of calls:
getItemCount()
viewHolder
with a call to the adapter's onCreateViewHolder()
viewHolder view
RecyclerView
places the list item on the screenviewHolder
's have been created to fill the screen, they are reusedRecyclerView
and Todo fragmentsRecyclerView
in the onCreateView
methodIn TodoListFragment:
private RecyclerView mTodoRecyclerView;
mTodoRecyclerView = (RecyclerView)
view.findViewById(R.id.todo_recycler_view);
// it will crash without a LayoutManager
mTodoRecyclerView.setLayoutManager(
new LinearLayoutManager(getActivity()) );
Fragments
, RecyclerView
has its own view
hierarchyIn fragment_todo_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/todo_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
ViewHolder
to inflate and fill the layoutIn TodoListFragment:
public class TodoHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
public TodoHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(
R.layout.list_item_todo, parent, false));
}
}
Adapter
and override three methodsIn TodoListFragment:
public class TodoAdapter extends
RecyclerView.Adapter<TodoListFragment.TodoHolder> {
private List<Todo> mTodos;
public TodoAdapter(List<Todo> todos) {
mTodos = todos;
}
@Override
public TodoListFragment.TodoHolder onCreateViewHolder(
ViewGroup parent, int viewType) {
LayoutInflater layoutInflater =
LayoutInflater.from(getActivity());
return new TodoHolder(layoutInflater, parent);
}
@Override
public void onBindViewHolder(
TodoHolder holder, int position) {
Todo todo = mTodos.get(position);
holder.bind(todo);
}
@Override
public int getItemCount() {
return mTodos.size();
}
}
Seperating creation and binding allows views to be (Recycled) reused
ViewHolder
constructorViewHolder
relies on a bind(data)
method to set the values of views it holds.
TodoModel
constructor and increase the loop to 30 test todos.
The code for this exercise can be found at: https://github.com/ebbi/todo-persistence-app
SQlite
— Create, Read, Update TodoTodo Fragment App
and Todo Component App
examples
From Android Documentation on android.database.sqlite
Caution: Although these APIs are powerful, they are fairly low-level and require a great deal of time and effort to use:
For these reasons, we highly recommended using the Room Persistence Library as an abstraction layer for accessing information in your app's SQLite databases.
package database;
import java.util.Date;
import java.util.UUID;
public class TodoDbSchema {
public static final class TodoTable {
public static final String NAME = "todos";
public static final class Cols {
public static final String UUID = "uuid";
public static final String TITLE = "title";
public static final String DETAIL = "detail";
public static final String DATE = "date";
public static final String IS_COMPLETE = "isComplete";
}
}
}
/* Columns can be refered to in a Java safe way */
TodoDbSchema.Cols.TITLE
SQLiteOpenHelper
class handles building a DBpackage database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import database.TodoDbSchema.TodoTable;
public class TodoBaseHelper extends SQLiteOpenHelper {
private static final int VERSION = 1;
private static final String DATABASE_NAME = "todo.db";
public TodoBaseHelper(Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table " + TodoTable.NAME + "(" +
TodoTable.Cols.UUID + ", " +
TodoTable.Cols.TITLE + ", " +
TodoTable.Cols.DETAIL + ", " +
TodoTable.Cols.DATE + ", " +
TodoTable.Cols.IS_COMPLETE + ")"
);
}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {
}
}
SQLiteOpenHelper
to create a DBpublic class TodoModel {
private static TodoModel sTodoModel;
private static Context mContext;
private SQLiteDatabase mDatabase;
public static TodoModel get(Context context) {
mContext = context.getApplicationContext();
if (sTodoModel == null) {
sTodoModel = new TodoModel(context);
}
return sTodoModel;
}
private TodoModel(Context context){
mContext = context.getApplicationContext();
mDatabase = new TodoBaseHelper(mContext)
.getWritableDatabase();
}
/* insert seed test data */
}
⋮
ContentValues
class to store key/value maps/* Model static method for ContentValues */
private static ContentValues getContentValues(Todo todo) {
ContentValues contentValues = new ContentValues();
contentValues.put(
TodoDbSchema.TodoTable.Cols.UUID, todo.getId().toString());
contentValues.put(
TodoDbSchema.TodoTable.Cols.TITLE, todo.getTitle());
contentValues.put(
TodoDbSchema.TodoTable.Cols.DETAIL, todo.getDetail());
contentValues.put(
TodoDbSchema.TodoTable.Cols.DATE, todo.getDate().getTime());
contentValues.put(
TodoDbSchema.TodoTable.Cols.IS_COMPLETE, todo.isComplete()==1 ? 1 : 0);
return contentValues;
}
public void addTodo(Todo todo){
ContentValues contentValues = getContentValues(todo);
/* contentValues = null raises an exception except
when 2nd parameter is null in which case a new row is inserted */
mDatabase.insert(TodoDbSchema.TodoTable.NAME, null, contentValues);
}
public void updateTodo(Todo todo){
String uuidString = todo.getId().toString();
ContentValues contentValues = getContentValues(todo);
/* stop sql injection, pass uuidString to new String
so, it is treated as string rather than code */
mDatabase.update(TodoDbSchema.TodoTable.NAME, contentValues,
TodoDbSchema.TodoTable.Cols.UUID + " = ?",
new String[] { uuidString });
}
SQLiteDatabase.query()
has many overloads
corresponding to a SQL query SELECT columns FROM Table WHERE wherArgs GROUPBY, HAVING, ORDERBY, LIMIT
.
SQLiteDatabase.query()
returns a cursor
object Cursor cursor = mDatabase.query(TodoDbSchema.TodoTable.NAME, … )
Cursor
interface provides random read-write access to the result set returned by a database query.cursor.getColumnIndex(TodoDbSchema.TodoTable.Cols.TITLE));
CursorWrapper
to subclass Cursor
public class TodoCursorWrapper extends CursorWrapper {
public TodoCursorWrapper(Cursor cursor){
super(cursor);
}
public Todo getTodo() {
String uuidString = getString(
getColumnIndex(TodoDbSchema.TodoTable.Cols.UUID));
String title = getString(
getColumnIndex(TodoDbSchema.TodoTable.Cols.TITLE));
String detail = getString(
getColumnIndex(TodoDbSchema.TodoTable.Cols.DETAIL));
Long date = getLong(
getColumnIndex(TodoDbSchema.TodoTable.Cols.DATE));
int isComplete = getInt(
getColumnIndex(TodoDbSchema.TodoTable.Cols.IS_COMPLETE));
Todo todo = new Todo(UUID.fromString(uuidString));
todo.setTitle(title);
todo.setDetail(detail);
todo.setDate(new Date(date));
todo.setComplete(isComplete);
return todo;
}
}
select
(Read) using TodoCursorWrapper
private TodoCursorWrapper queryTodoList(
String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(
TodoDbSchema.TodoTable.NAME, null, whereClause, whereArgs, …
);
return new TodoCursorWrapper(cursor);
}
TodoCursorWrapper
public Todo getTodo(UUID id){
TodoCursorWrapper cursor = queryTodoList(
TodoDbSchema.TodoTable.Cols.UUID + " = ?",
new String[] {id.toString() }
);
try {
if (cursor.getCount() == 0) {
return null;
}
cursor.moveToFirst();
return cursor.getTodo();
} finally {
cursor.close();
}
}
TodoCursorWrapper
public List<Todo> getTodoList() {
List<Todo> todoList = new ArrayList<>();
TodoCursorWrapper cursor = queryTodoList(null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
todoList.add(cursor.getTodo());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return todoList;
}
SQLiteOpenHelper
change the version number and update the tables in the onUpgrade
method
SQLiteOpenHelper.onCreate()
is called and a new database instance is created).SQL
statement for correct syntax.
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
The code for this exercise can be found at: https://github.com/ebbi/TodoMVVM
MVVM is the Android recommended architecture. This lab exercise is an MVVM implementation of the Todo app.
MVVM is the Android recommended app architecture built arround a set of Architecture Components designed to work together. The components are lifecycle aware and make code design, implementation and maintenance much easier to manage.
Entity
Room
ORM classSQLite
databaseRoom
persistence library on its own threadDAO
SQL
queriesRoom
databaseDAO
to issue queries to the SQLite
database ( and thnkfully, hides SQLiteOpenHelper
)Repository
Repository
is also used to manage multiple data sources.ViewModel
Repository
and the UI. ViewModel
instances survive Activity
/Fragment
recreation.LiveData
LiveData
is aware of the relevant lifecycle status changes while observing.Fragment
transactions and seperation of concerns is achieved with ViewModel
and LiveData
implementing the observer pattern.
ViewModel
allows data to survive accross process rather than Activity
lifecycle, hence, configuration changes (such as screen rotations) are easier to manage.
LiveData
implements the observer pattern and is an observable data-holder class and a lifecycle aware component. UI Controllers can observe relevant data and LiveData
can notify them of data changes. This allows for views to be built only when actual changes to data occurs.
The Room
persistency library provides ORM and makes it easy to build a local cache layer, improving performance and the user experience with less reliance on data pulled from the network.
Create a new empty project named, TodoMVVM
API 27: Android 8.1 (Oreo)
Virtual Device: Nexus 5X API 27
Edit build.gradle (Project: TodoMVVM) and add:
ext {
roomVersion = '2.2.1'
archLifecycleVersion = '2.2.0-rc01'
coreTestingVersion = '2.1.0'
materialVersion = '1.0.0'
}
Edit build.gradle (Module: TodoMVVM) for dependencies
(insert at the end of the dependencies block, before the } )
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
// UI
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Testing
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
Sync Now (in Android Studio, top right corner)
Do not proceed unless the Sync completes without errors!
Entity
An entity
is an annotated class that describes a database table. Room uses the properties of this entity class to create columns in the database table. The same entity
is used to instantiate objects from rows of data in the database.
Create a new class file named Todo
and insert the following code:
Todo
entityimport androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/**
* A basic class representing a two-column todo_database table.
*
* @ Entity - annotate the class as an entity and supply a table name if not class name.
* @ PrimaryKey - identify the primary key.
* @ ColumnInfo - supply the column name if it is different from the variable name.
*
* See the documentation for the full set of annotations.
* https://developer.android.com/topic/libraries/architecture/room.html
*/
@Entity(tableName = "todo_table")
public class Todo {
@PrimaryKey
@NonNull
@ColumnInfo(name = "title")
private String mTitle;
private String mDetail;
public Todo(@NonNull String title) {
this.mTitle = title;
}
public String getTitle() {
return this.mTitle;
}
String getDetail() {
return this.mDetail;
}
void setDetail(String mDetail) {
this.mDetail = mDetail;
}
}
The class is a POJO with a set of attributes representing the database columns and getter, setter methods. Note the annotations identify how each part of this class relates to an entry in the database. Room
uses this information to generate code.
@Entity(tableName = "todo_table")
@Entity
class represents a SQLite table. The parameter tablename
can optionally set a different name than the default name of the class.@PrimaryKey
tittle
is the primary key.@NonNull
@ColumnInfo(name = "title")
getTitle()
and getDetail()
methods.The complete list of annotations can be found in the Room package summary reference. And see Defining data using Room entities for further examples uses of annotations.
API and dependancies are now setup, Run to make sure there are no errors Create a git branch, entity add code for Todo Git checkout master and merge entity branch Create a git branch, dao add code for TodoDao Git checkout master and merge entity branch Create a git branch, room add code for TodoRoomDatabase Git checkout master and merge room branch
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
The Introduction to Programming in Java is a good concise reference. See the lecture notes and complete the exercises. This is a minimum needed before attempting to code in any framework such as Android.
static
types and methods?if (true) { continue to lab exercises } else { Read Introduction to Programming in Java ‐ from MIT Watch Java Memory Management, video from Virtual Pair Programmers Think Java is a good online reference book Use the android and stackoverflow search in the header }
Thread
is a sequence of instructions that can be managed independantly by a scheduler
Thread
s a Hello World exampleRunnable Interface
public void
with no argumentspublic void run()
Thread
) World (on another Thread
)public class ThreadDemo {
public static void main(String args[]) {
System.out.println("Hello (on a thread) World (on another thread)!");
/* annoymous class implementing Runnable interface run()
method passed to Runnable */
Runnable objHello = new Runnable() {
public void run() {
for(int i=1; i<=5; i++) {
System.out.println("Hello ");
/* do some nice processing! */
try { Thread.sleep(1000); } catch(Exception e){}
}
}
};
Runnable objWorld = new Runnable() {
/* annoymous class with run() method passed to Runnable */
public void run() {
for(int i=1; i<=5; i++) {
System.out.println("World");
try { Thread.sleep(3000); } catch(Exception e){}
}
}
};
Thread threadHello = new Thread(objHello);
Thread threadWorld = new Thread(objWorld);
threadHello.start();
try { Thread.sleep(1000); } catch(Exception e){}
threadWorld.start();
// System.out.println(threadHello.isAlive());
// try { threadHello.sleep(1000); } catch(Exception e){}
/* .join will force the calling thread to wait until it complete */
// try { threadHello.join(); } catch(Exception e){}
// try { threadWorld.join(); } catch(Exception e){}
/* check if method is alive after join */
System.out.println(threadHello.getName() + " " + threadHello.isAlive());
System.out.println(threadWorld.getName() + " " + threadWorld.isAlive());
System.out.println("Time for poetry");
}
}
Thread
s a Hello World Synchronised exampleRunnable Interface
public void
with no argumentspublic void run()
Thread
class DoWork {
/* Class to run in a thread
Simulate the load by the number of calls to increment */
int count;
/* needs to be synchronized and made thread safe */
public synchronized void increment() {
count++; // increment is more than one operation!
}
}
public class SyncDemo {
public static void main(String args[]) {
System.out.println("Synchronised value from both Hello and World threads");
DoWork doWork = new DoWork();
/* annoymous class implementing Runnable interface run()
method passed to Runnable */
Runnable objHello = new Runnable() {
public void run() {
for(int i=1; i<=1000; i++) {
doWork.increment();
}
System.out.println( doWork.count + " Hello ");
}
};
Runnable objWorld = new Runnable() {
public void run() {
for(int i=1; i<=1000; i++) {
doWork.increment();
}
System.out.println( doWork.count + " World ");
}
};
Thread threadHello = new Thread(objHello);
Thread threadWorld = new Thread(objWorld);
threadHello.start();
threadWorld.start();
/* .join will force the calling thread to wait until it complete */
// try { threadHello.join(); } catch(Exception e){}
// try { threadWorld.join(); } catch(Exception e){}
System.out.println(doWork.count + " reminders for poetry");
}
}
It is worth while investing time to know git; version control and collaboration is essential.
Use an existing account to signin OR create a new account on Github or Bitbucket.
(Note: You must complete the account creation by email verification.)
Once signed in to the remote repo (Github or Bitbucket), creat a new repository. (for the Repository name, use the project name) and leave the rest as default.
Once the remote repository is created, instructions to add this remote repository (or origin) to the local git repository is displayed.
echo "# insert name of the project" >> README.md
git init
git add .
git commit -am "first commit"
git remote add origin (insert the repo address)
git push -u origin master
Note: repo address is similar to:
https://github.com/you!/your_project_name!.git
A typical workflow to create a branch, try changes, and either delete or merge the changes
Create a new empty folder. Use a terminal and change directory (cd) to the folder. Edit a test file with a message, "Hello World" and save it.
git init
git status
git add .
git commit -am "initial setup"
git status
git log
git branch xyz
git checkout xyz
git status
git log
Edit the test file and add a message "Happy World!" and also duplicate the file
To reverse the changes:
git add .
git reset --hard HEAD
git status
Note the change have been reversed.
To commit the changes and merge it to the master branch:
git status
git add .
git commit -am "added happy and duplicated test file"
git checkout master
git merge xyz
git status
git log
Note, status
and log
are for information only.
git branch
‐ lists all branches
git push origin master
‐ copies the current master to the remote repo
Activity
and Intent
Activity
and Intent
Activity
— a single screen in an app
Intent
— a message object to start an activity
Activity
and Intent
communicate via the OS ActivityManager
Activity
in one App to use Activity
in another App
Activity
Activity
Activity
in onCreate()
callback
setContentView( findViewById( R.layout.activity_example )
onSaveInstanceState( Bundle )
onPause()
called when user leaves the activity
— serialise
Activity
in AndroidManifest.xml
Activity
state, persistance, and lifecycle
Activity
has four statesactive
(or running at the top of the activity stack)
paused
— alive in memeory, visible, lost focus
stopped
— obscured by another activity, alive im memory, not visible
paused
or stopped
— may lead to destroyed
and restarted
to previous state
Activity
‐ three key loopsonCreate
to onDestroy
onStart
to onStop
onResume
to onPause
Source code: https://github.com/ebbi/helloToast
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
...
}
@Override
public void onSaveInstanceState(Bundle outState) {
Log.d(LOG_TAG, "onSaveInstanceState");
...
}
@Override
public void onStart(){
super.onStart();
Log.d(LOG_TAG, "onStart");
}
@Override
protected void onPause() {
super.onPause();
Log.d(LOG_TAG, "onPause");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(LOG_TAG, "onRestart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(LOG_TAG, "onResume");
}
@Override
protected void onStop() {
super.onStop();
Log.d(LOG_TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
}
Bundle
key, value pairs
Source code: https://github.com/ebbi/helloToast
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Restore the state.
if (savedInstanceState != null) {
mCount = savedInstanceState.getInt("count");
mShowCount.setText(String.valueOf(mCount));
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("count", mCount);
}
Lifecycle
class and observer patternevents
— enumeration from framework and lifecycle
class
lifecycle
class events map to callback
methods
lifecycle
object
LifecycleObserver
allows for lifecycle-aware components (observer pattern to monitor lifecycle status)
Intent
Intent
— messaging object to request an action from another app component
Intent
— a passive data structure that holds an abstract description of an operation to be performed
Intent
constructorsIntent()
Intent( String action )
Intent( String action, URI uri )
Intent( Context context, Class<?>cls )
Intent( String action, Context packageContext, Class<?>cls )
Intent
examplessetAction(), putExtra, addCategory(), …
Intent
— action is defined, target component is unkownIntent intent = new Intent();
intent.setAction( Intent.ACTION_WEB_SEARCH );
intent.putExtra( SearchManager.QUERY,"search text" );
Intent browserIntent = new Intent(
Intent.ACTION_VIEW,
Uri.parse( "http://www.ecosia.com" )
);
browserIntent.addCategory( CATEGORY_BROWSABLE );
Intent
— target component is knownIntent intent = new Intent(
packageContext, ExampleActivity.class
);
intent.putExtra( "EX1","example value" );
intent.putExtra( "EX2","example value" );
late runtime binding between the code in different applications
intent
be instantiated?"from each according to their ability, to each according to their need"
Activity
has the Intent
data
Activity
uses the Intent
data
static
method in called Activity
public static Intent newIntent(
Context packageContext, int todoIndex){
Intent intent = new Intent(
packageContext, TodoDetailActivity.class);
intent.putExtra(TODO_INDEX,todoIndex);
return intent;
}
The calling activity:
Intent intent =
TodoDetailActivity.newIntent(TodoActivity.this, mTodoIndex);
Intent
result back
Activity
method startActivityForResult(Intent, requestCode)
startActivityForResult(Intent intent, int requestCode);
@Override
public void onClick(View v) {
Intent intent = TodoDetailActivity.newIntent(
TodoActivity.this, mTodoIndex);
// requestCode = int constant for a single activity
startActivityForResult(intent, IS_SUCCESS);
}
Activity
implement two method for setting results
setResult(int resultCode);
setResult(int resultCode, Intent intent);
resultCode
, is Activity.RESULT_OK
or Activity.RESULT_CANCELED
extras
in intent
for more data
Intent intent = new Intent();
intent.putExtra(IS_TODO_COMPLETE, isChecked);
setResult(RESULT_OK, intent);
Callback onActivityResult()
to retrieve any set result.
@Override
protected void onActivityResult(
int requestCode, int resultCode, Intent intent) {
if (intent != null) {
// data in intent from child activity
boolean isTodoComplete =
intent.getBooleanExtra(IS_TODO_COMPLETE, false);
…
}
}
interface
in the View
class that contains a single callback method
View.OnClickListener onClick()
View.OnLongClickListener onLongClick()
View.OnFocusChangeListener OnFocusChangeListener
View.OnKeyListener OnKeyListener()
View.OnTouchListener OnTouchListener()
View.OnCreateContextMenuListener OnCreateContextMenuListener()
View.onClickListener
fires an event that results in the corresponding event handler, onClick
call back method being called.
private
data conveniently available to anonymous inner class
listener
for each view
object
buttonNext.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
// do something when the button is clicked
}
});
interface
in the class definitionpublic class ActivityMain extends Activity
implements View.OnClickListener {
protected void onCreate(Bundle savedValues) {
…
Button button = (Button)findViewById(R.id.next);
button.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// do something when the button is clicked
{
…
}
interface
for any view objects/* Create an anonymous implementation of OnClickListener
for all clickable view objects */
private View.OnClickListener mTodoListener =
new View.OnClickListener() {
public void onClick(View v) {
// get the clicked object and do something
switch (v.getId() {
case R.id.checkBoxIsComplete:
default:
break;
}
}
};
And the usage would be:
CheckBox checkboxIsComplete =
(CheckBox)findViewById(R.id.checkBoxIsComplete);
checkboxIsComplete.setOnClickListener(mTodoListener);
/*
listener implementation with the method name
defined in the view definition
*/
android:onClick = "onCheckboxIsCompleteClick"
with Activity method:
public void onCheckboxIsCompleteClick(View view) { … }
ViewModel
retrieve data from model when requested from the viewActivities/Fragments
become very light weightViewModel
are decoupled from the viewViewModel
have process scopePresenter
calls the view to display
ViewModel
exposes stream of events for Views
to bind to. No need for all the MVP interfaces.
Observable
(Java 9 deprecated Observable)