Layout the PlacemarkActivity - supporting create/edit of placemarks
Before proceeding to introduce new features into our app - we will augment some of the libraries:
...
implementation 'com.android.support:design:28.0.0'
...
Make sure you include the above in the correct build.gradle file (there are two). Append it to the other implementation
entries. When you make the above change you will be invited to sync
the project again - do this now.
This is an additional library to support android components and layouts not included in the base SDK:
Also, change our theme:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
This replaces the 'DarkActionBar' theme in the generated style
Now change the colours:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FFFFFF</color>
<color name="colorPrimaryDark">#000000</color>
<color name="colorAccent">#4c90af</color>
</resources>
Or choose other colours as you please...
We also need a new string for the button, which we will introduce in the next step:
<string name="button_addPlacemark">Add Placemark</string>
Working with activity_placemark.xml
, progressively introduce the following versions - replacing each with the successive content below. Keep an eye on the design
view as you progress to notice the changes
This is what you currently have (select the Text
view to reveal this):
<?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="org.wit.placemark.PlacemarkActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
Now make tee following change ... replace the current contents to include relative layout + toolbar
<?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="org.wit.placemark.PlacemarkActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:fitsSystemWindows="true"
app:elevation="0dip"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbarAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@color/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
Keep an eye on the Design view as you make this change.
... augment with a scroll view containing a linear layout:
<?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="org.wit.placemark.PlacemarkActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:fitsSystemWindows="true"
app:elevation="0dip"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbarAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@color/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/appBarLayout"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
... scroll view linear layout contains a text edit field:
<?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="org.wit.placemark.PlacemarkActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:fitsSystemWindows="true"
app:elevation="0dip"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbarAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@color/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/appBarLayout"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputEditText
android:id="@+id/placemarkTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:hint="@string/hint_placemarkTitle"
android:inputType="text"
android:maxLength="25"
android:maxLines="1"
android:padding="8dp"
android:textColor="@color/colorPrimaryDark"
android:textSize="14sp"/>
</LinearLayout>
</ScrollView>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
The above needs a new string resource:
<resources>
<string name="app_name">Placemark</string>
<string name="hint_placemarkTitle">Placemark Title</string>
</resources>
... a new button in the layout
<?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="org.wit.placemark.PlacemarkActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:fitsSystemWindows="true"
app:elevation="0dip"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbarAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@color/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/appBarLayout"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputEditText
android:id="@+id/placemarkTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:hint="@string/hint_placemarkTitle"
android:inputType="text"
android:maxLength="25"
android:maxLines="1"
android:padding="8dp"
android:textColor="@color/colorPrimaryDark"
android:textSize="14sp"/>
<Button
android:id="@+id/btnAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@color/colorAccent"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:stateListAnimator="@null"
android:text="@string/button_addPlacemark"
android:textColor="@color/colorPrimary"
android:textSize="16sp"/>
</LinearLayout>
</ScrollView>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
Building layouts textually like this is a useful skill to acquire over time. Initially, you will probably be most comfortable with drawing the layouts using the design view.
Make sure the application launches now - and the design view is presented in the running app
This short article on layouts is as useful primer:
We are going to be working exclusively in Kotlin - not Java. This affords considerable improvements, largely around conciseness and expressiveness of code.
To prepare for this, include these additional dependencies in our gradle.build:
...
implementation 'org.jetbrains.anko:anko:0.10.7'
...
This library is documented here:
We will be progressively introducing some of its features as we evolve the application.
The first feature is a simple way of logging:
Change the class to include the 'AnkoLogger' feature:
..
class PlacemarkActivity : AppCompatActivity(), AnkoLogger {
...
When you make this change, AnkoLogger will not be recognised - so you will need to import it. This can be triggered automatically (if you can figure this out). The correct import statement is:
import org.jetbrains.anko.AnkoLogger
Then, in our onCreate() method, try it out:
...
info("Placemark Activity started..")
...
Again, this will require an import - which will be this:
import org.jetbrains.anko.info
Try to figure out the key strokes required to generate this...
Now, make sure you can run the app and see the log messages in the 'Logcat' view in Studio:
Do not proceed further until you can locate something like the above in Logcat. The logs are a bit noisy, and may contain what look like errors. However, buried in there should be your message.
This style of logging is discussed here:
Now include the following inside the onCreate function:
btnAdd.setOnClickListener() {
info("add Button Pressed")
}
Introducing this will require additional imports - get used to selecting these as prompted by Studio. On this occasion you will be presented with a choice of imports:
We need to select the synthetic import.The complete class will look like this:
package org.wit.placemark
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_placemark.*
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.info
class PlacemarkActivity : AppCompatActivity(), AnkoLogger {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_placemark)
info("Placemark Activity started..")
btnAdd.setOnClickListener() {
info("add Button Pressed")
}
}
}
Make sure you can run the app and that you can see the 'add button pressed' log.
Read this short blog post outlining how the above code differers from the traditional java implementation of same.
Try this alternative implementation of the event handler:
btnAdd.setOnClickListener() {
val placemarkTitle = placemarkTitle.text.toString()
if (placemarkTitle.isNotEmpty()) {
info("add Button Pressed: $placemarkTitle")
}
else {
toast ("Please Enter a title")
}
}
Try it out and notice the difference - particularly if you press add without entering a title...
In the above we are using anko toasts:
Refactor the application structure such that ActivityPlacemark is in a new package called 'org.wit.placemarks.activities':
You should be able to do this from within the Studio Android perspective. You will be using the context menu to create the new package in the java folder:
and then dragging/dropping the class into this new package. This will automatically trigger refactor step:
Once this is completed, a number of files will be automatically changed:
See if you can locate each of the changes...
Introduce a new package called 'models' as shown here:
... and bring in this new class:
package org.wit.placemark.models
data class PlacemarkModel(var title: String = "")
This is an example of a Kotlin Data class:
Briefly review the above - this is the official documentation:
Here is a new version of PlacemarkActivity that makes use of this model:
package wit.org.placemark.activities
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_placemark.*
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.info
import org.jetbrains.anko.toast
import org.wit.placemark.models.PlacemarkModel
import wit.org.placemark.R
class PlacemarkActivity : AppCompatActivity(), AnkoLogger {
var placemark = PlacemarkModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_placemark)
btnAdd.setOnClickListener() {
placemark.title = placemarkTitle.text.toString()
if (placemark.title.isNotEmpty()) {
info("add Button Pressed: $placemark")
}
else {
toast ("Please Enter a title")
}
}
}
}
Read it carefully - notice how we are creating a placemark as a class member:
var placemark = PlacemarkModel()
and then using it in the event handler:
btnAdd.setOnClickListener() {
placemark.title = placemarkTitle.text.toString()
if (placemark.title.isNotEmpty()) {
info("add Button Pressed: $placemark")
}
else {
toast ("Please Enter a title")
}
}
Placemark application so far:
Download, expand and open in Studio the sample solution above. Run it in the emulator.
Make sure you can auto import the correct libraries when you introduce referenced. As an exercise, delete all of the imports from PlacemarkActivity:
package org.wit.placemark.activities
// deleted imports..
class PlacemarkActivity : AppCompatActivity(), AnkoLogger {
var placemark = PlacemarkModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_placemark)
info("Placemark Activity started..")
btnAdd.setOnClickListener() {
placemark.title = placemarkTitle.text.toString()
if (placemark.title.isNotEmpty()) {
info("add Button Pressed: $placemarkTitle")
}
else {
toast ("Please Enter a title")
}
}
}
}
NOw reintroduce them one-by-one. Keep a close eye on any choices you make as you do this.
Create an ArrayList of Placemarks in PlacemarkActivity - and add each new placemark to this list.
Log all placemarks when a new one is entered.
HINT: Here is a simple placemark array declaration + creation:
val placemarks = ArrayList<PlacemarkModel>()
This is the documentation on lists in general:
Greater emphasis is placed here on mutability - which we can explore at a later stage.
Create new text field description
+ log when entered. This will require you to: