Chapters

Hide chapters

Android Apprentice

Fourth Edition · Android 11 · Kotlin 1.4 · Android Studio 4.1

Section II: Building a List App

Section 2: 7 chapters
Show chapters Hide chapters

Section III: Creating Map-Based Apps

Section 3: 7 chapters
Show chapters Hide chapters

17. Detail Activity
Written by Kevin D Moore

Heads up... You’re accessing parts of this content for free, with some sections shown as vlgyswgez text.

Heads up... You’re accessing parts of this content for free, with some sections shown as nxkowwkyv text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In this chapter, you’ll add the ability to edit bookmarks. This involves creating a new Activity to display the bookmark details with editable fields.

Getting started

If you’re following along with your own app, copy ic_action_done.png from the starter project in the res/drawable-xxxx/ folders into your project. Make sure to copy the files from all of the drawable folders, including everything with the .hdpi, .mdpi, .xhdpi, and .xxhdpi extensions.

If you want to use the starter project instead, locate the projects folder for this chapter and open the PlaceBook app inside the starter folder. If you do use the starter app, don’t forget to add your google_maps_key in google_maps_api.xml. Read Chapter 13 for more details about the Google Maps key.

The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.

Fixing the info window

Before moving on, you need to track down and fix that pesky bug left over from the previous chapter where the app crashes when tapping on a blue marker. The desired behavior is:

Heads up... You’re accessing parts of this content for free, with some sections shown as dfmerhfiw text.

Heads up... You’re accessing parts of this content for free, with some sections shown as slhykzjin text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
com.raywenderlich.placebook.adapter.BookmarkInfoWindowAdapter.getInfoContents

imageView.setImageBitmap(
    (marker.tag as MapsActivity.PlaceInfo).image)
when (marker.tag) {
  // 1
  is MapsActivity.PlaceInfo -> {
    imageView.setImageBitmap(
        (marker.tag as MapsActivity.PlaceInfo).image)
  }
  // 2  
  is MapsViewModel.BookmarkMarkerView -> {
    val bookMarkview = marker.tag as
        MapsViewModel.BookmarkMarkerView
    // Set imageView bitmap here
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as wfmyrdvom text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wjwekslis text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Saving an image

Although you can add an image directly to the Bookmark model class and let the Room library save it to the database, it’s not a best practice to store large chunks of data in the database. A better method is to store the image as a file that’s linked to the record in the database.

// 1
object ImageUtils {
  // 2
  fun saveBitmapToFile(context: Context, bitmap: Bitmap,
      filename: String) {      
    // 3
    val stream = ByteArrayOutputStream()
    // 4
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
    // 5
    val bytes = stream.toByteArray()
    // 6
    saveBytesToFile(context, bytes, filename)
  }
  // 7
  private fun saveBytesToFile(context: Context, bytes:
      ByteArray, filename: String) {
    val outputStream: FileOutputStream
    // 8
    try {    
      // 9
      outputStream = context.openFileOutput(filename,
          Context.MODE_PRIVATE)
      // 10
      outputStream.write(bytes)
      outputStream.close()
    } catch (e: Exception) {
      e.printStackTrace()
    }
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as htliqwdah text.

Heads up... You’re accessing parts of this content for free, with some sections shown as cbbibwvab text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
{
  // 1
  fun setImage(image: Bitmap, context: Context) {
    // 2
    id?.let {
      ImageUtils.saveBitmapToFile(context, image,
          generateImageFilename(it))
    }
  }
  //3
  companion object {
    fun generateImageFilename(id: Long): String {
      // 4
      return "bookmark$id.png"
    }
  }
}

Adding the image to the bookmark

Next, you need to set the image for a bookmark when it’s added to the database.

image?.let { bookmark.setImage(it, getApplication()) }

Heads up... You’re accessing parts of this content for free, with some sections shown as ktbibbgef text.

Heads up... You’re accessing parts of this content for free, with some sections shown as rlsoxckas text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Simplifying the bookmark process

Before testing this new functionality there’s a small change you can make to simplify the process of adding a new bookmark.

marker?.showInfoWindow()

Using Device File Explorer

If you want to verify the image was saved and take a peek behind the scenes at how Android stores files, you can use the Device File Explorer in Android Studio. This is a handy tool for working directly with the Android file system.

Heads up... You’re accessing parts of this content for free, with some sections shown as dwcukmpor text.

Heads up... You’re accessing parts of this content for free, with some sections shown as flcowqxim text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Loading an image

It’s time to load the image from a file. This is considerably easier than saving an image because Android provides a method on BitmapFactory for loading images from files.

fun loadBitmapFromFile(context: Context, filename: String): Bitmap? {
  val filePath = File(context.filesDir, filename).absolutePath
  return BitmapFactory.decodeFile(filePath)
}

Updating BookmarkMarkerView

Now that you can load the image from where it’s stored, it’s time to update BookmarkMarkerView to provide the image for the View.

Loading images on-demand

Open MapsViewModel.kt and replace the BookmarkMarkerView data class definition with the following:

data class BookmarkMarkerView(
    var id: Long? = null,
    var location: LatLng = LatLng(0.0, 0.0)
) {
  fun getImage(context: Context) = id?.let {
      ImageUtils.loadBitmapFromFile(context, Bookmark.generateImageFilename(it))
  }
}
class BookmarkInfoWindowAdapter(val context: Activity) :
    GoogleMap.InfoWindowAdapter {
imageView.setImageBitmap(bookMarkview.getImage(context))

Heads up... You’re accessing parts of this content for free, with some sections shown as crpiztgyb text.

Heads up... You’re accessing parts of this content for free, with some sections shown as rvhuxtlus text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Updating the Info window

Open MapsViewModel.kt and update the BookmarkMarkerView declaration to match the following:

data class BookmarkMarkerView(
    var id: Long? = null,
    var location: LatLng = LatLng(0.0, 0.0),
    var name: String = "",
    var phone: String = ""
) {
private fun bookmarkToMarkerView(bookmark: Bookmark) = BookmarkMarkerView(
      bookmark.id,
      LatLng(bookmark.latitude, bookmark.longitude),
      bookmark.name,
      bookmark.phone
)
val marker = map.addMarker(MarkerOptions()
    .position(bookmark.location)
    .title(bookmark.name)
    .snippet(bookmark.phone)
    .icon(BitmapDescriptorFactory.defaultMarker(
        BitmapDescriptorFactory.HUE_AZURE))
    .alpha(0.8f))

Heads up... You’re accessing parts of this content for free, with some sections shown as ldrukjwet text.

Heads up... You’re accessing parts of this content for free, with some sections shown as tnridxkum text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Bookmark detail activity

You’ve waited patiently, and it’s finally time to build out the detail Activity for editing a bookmark. For that, you’ll add a new screen that allows the user to edit key details about the bookmark, along with a custom note. You’ll do this by creating a new Activity that displays when a user taps on an Info window.

Designing the edit screen

Before creating the Activity, let’s go over the screen layout and the main elements that will be incorporated.

The Bookmark Edit Layout
Qvi Yoajvadt Uvuh Wolaev

Introducing Data Binding

Previously Google recommended using Kotlin Android extensions for referring to layout fields in classes. Now Google recommends using either View Binding or Data Binding. From Google:

Heads up... You’re accessing parts of this content for free, with some sections shown as zvkiqfsuh text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lpnehflys text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
buildFeatures {
  viewBinding true
  dataBinding true  // add this line
}

Defining styles

First, you need to define some standard styles that are required when using the support library version of the toolbar.

<style name="AppTheme.NoActionBar">
  <item name="windowActionBar">false</item>
  <item name="windowNoTitle">true</item>
</style>

<style name="AppTheme.AppBarOverlay" 
       parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" 
       parent="ThemeOverlay.AppCompat.Light" />
<style name="BookmarkLabel">
  <item name="android:layout_width">0dp</item>
  <item name="android:layout_height">wrap_content</item>
  <item name="android:layout_weight">0.2</item>
  <item name="android:layout_gravity">bottom</item>
  <item name="android:layout_marginStart">8dp</item>
  <item name="android:layout_marginLeft">8dp</item>
  <item name="android:layout_marginBottom">4dp</item>
  <item name="android:gravity">bottom</item>
</style>

<style name="BookmarkEditText">
  <item name="android:layout_width">0dp</item>
  <item name="android:layout_weight">0.8</item>
  <item name="android:layout_height">wrap_content</item>
  <item name="android:layout_marginEnd">8dp</item>
  <item name="android:layout_marginRight">8dp</item>
  <item name="android:layout_marginStart">8dp</item>
  <item name="android:layout_marginLeft">8dp</item>
  <item name="android:ems">10</item>
</style>

Creating the details layout

Finally, you need to create the bookmark details Layout based on the design. The Activity will use all of the new styles you just added to the project.

implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation 'com.google.android.material:material:1.2.1'

Heads up... You’re accessing parts of this content for free, with some sections shown as rqpijkhyd text.

Heads up... You’re accessing parts of this content for free, with some sections shown as fckakkmaj text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
<string name="name">Name</string>
<string name="address">Address</string>
<string name="phone">Phone</string>
<string name="phone_number">Phone number</string>
<string name="notes">Notes</string>
<string name="enter_notes">Enter notes</string>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto">
  <androidx.coordinatorlayout.widget.CoordinatorLayout    
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.google.android.material.appbar.AppBarLayout
      android:id="@+id/appbar"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">
        <com.google.android.material.appbar.MaterialToolbar
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          app:contentScrim="?attr/colorPrimary"
          app:layout_scrollFlags="scroll|exitUntilCollapsed"
          app:toolbarId="@+id/toolbar">
          <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/AppTheme.PopupOverlay" />
        </com.google.android.material.appbar.MaterialToolbar>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
      android:layout_width="match_parent"
      android:layout_height="match_parent"                                           app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">

      <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.appcompat.widget.AppCompatImageView
          android:id="@+id/imageViewPlace"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:adjustViewBounds="true"
          android:maxHeight="300dp"
          android:scaleType="fitCenter"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent"
          app:srcCompat="@drawable/default_photo" />
      </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.core.widget.NestedScrollView>
  </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

<androidx.appcompat.widget.AppCompatTextView
  android:id="@+id/textViewName"
  style="@style/BookmarkLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/name"
  android:layout_marginTop="16dp"
  app:layout_constraintBaseline_toBaselineOf="@+id/editTextName"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/imageViewPlace" />

<androidx.appcompat.widget.AppCompatTextView
  android:id="@+id/textViewNotes"
  style="@style/BookmarkLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:inputType="textMultiLine"
  android:text="@string/notes"
  app:layout_constraintBaseline_toBaselineOf="@+id/editTextNotes"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/textViewName" />

<androidx.appcompat.widget.AppCompatTextView
  android:id="@+id/textViewPhone"
  style="@style/BookmarkLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/phone"
  app:layout_constraintBaseline_toBaselineOf="@+id/editTextPhone"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/textViewNotes" />

<androidx.appcompat.widget.AppCompatTextView
  android:id="@+id/textViewAddress"
  style="@style/BookmarkLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/address"
  app:layout_constraintBaseline_toBaselineOf="@+id/editTextAddress"
  app:layout_constraintEnd_toStartOf="@+id/barrier1"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/textViewPhone" />

<androidx.constraintlayout.widget.Barrier
  android:id="@+id/barrier1"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:barrierDirection="start"
app:constraint_referenced_ids="editTextName, editTextNotes,editTextPhone, editTextAddress" />

Heads up... You’re accessing parts of this content for free, with some sections shown as tghurxhof text.

Heads up... You’re accessing parts of this content for free, with some sections shown as vkgyqvmyt text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

<com.google.android.material.textfield.TextInputEditText
  android:id="@+id/editTextName"
  style="@style/BookmarkEditText"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:hint="@string/name"
  android:layout_marginTop="16dp"
  android:layout_marginStart="16dp"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toEndOf="@+id/barrier1"
  app:layout_constraintTop_toBottomOf="@+id/imageViewPlace" />

<com.google.android.material.textfield.TextInputEditText
  android:id="@+id/editTextNotes"
  style="@style/BookmarkEditText"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:hint="@string/enter_notes"
  android:layout_marginStart="16dp"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toEndOf="@+id/barrier1"
  app:layout_constraintTop_toBottomOf="@+id/editTextName" />

<com.google.android.material.textfield.TextInputEditText
  android:id="@+id/editTextPhone"
  style="@style/BookmarkEditText"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:hint="@string/phone_number"
  android:layout_marginStart="16dp"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toEndOf="@+id/barrier1"
  app:layout_constraintTop_toBottomOf="@+id/editTextNotes" />

<com.google.android.material.textfield.TextInputEditText
  android:id="@+id/editTextAddress"
  style="@style/BookmarkEditText"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:hint="@string/address"
  android:inputType="textMultiLine"
  android:layout_marginStart="16dp"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toEndOf="@+id/barrier1"
  app:layout_constraintTop_toBottomOf="@+id/editTextPhone" />

Details activity class

Now that the bookmark details Layout is complete, you can create the details Activity to go along with it.

class BookmarkDetailsActivity : AppCompatActivity() {
  private lateinit var databinding: ActivityBookmarkDetailsBinding
  
  override fun onCreate(savedInstanceState: android.os.Bundle?) {
    super.onCreate(savedInstanceState)
    databinding = DataBindingUtil.setContentView(this, R.layout.activity_bookmark_details)
    setupToolbar()
  }

  private fun setupToolbar() {
    setSupportActionBar(databinding.toolbar)
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as hnfyhqsem text.

Heads up... You’re accessing parts of this content for free, with some sections shown as tnpamsgin text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Updating the manifest

Next, open AndroidManifest.xml and replace the BookmarkDetailsActivity activity with:

<activity
    android:name=
        "com.raywenderlich.placebook.ui.BookmarkDetailsActivity"
    android:label="Bookmark"
    android:theme="@style/AppTheme.NoActionBar"
    android:windowSoftInputMode="stateHidden">
</activity>

Starting the details Activity

You can now hook up the new details Activity to the main maps Activity. You’ll detect when the user taps on a bookmark Info window, and then start the details Activity.

private fun startBookmarkDetails(bookmarkId: Long) {
  val intent = Intent(this, BookmarkDetailsActivity::class.java)
  startActivity(intent)
}
private fun handleInfoWindowClick(marker: Marker) {
  when (marker.tag) {
    is PlaceInfo -> {
      val placeInfo = (marker.tag as PlaceInfo)
      if (placeInfo.place != null && placeInfo.image != null) {
        GlobalScope.launch {
          mapsViewModel.addBookmarkFromPlace(placeInfo.place,
              placeInfo.image)
        }
      }
      marker.remove();
    }
    is MapsViewModel.BookmarkMarkerView -> {
      val bookmarkMarkerView = (marker.tag as
          MapsViewModel.BookmarkMarkerView)
      marker.hideInfoWindow()
      bookmarkMarkerView.id?.let {
        startBookmarkDetails(it)
      }
    }
  }
}

Populating the bookmark

The Activity has the general look you want, but it lacks any knowledge about the bookmark. To fix this, you’ll pass the bookmark ID to the Activity so that it can display the bookmark data.

Heads up... You’re accessing parts of this content for free, with some sections shown as xlhowqqaj text.

Heads up... You’re accessing parts of this content for free, with some sections shown as crfawjdur text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
const val EXTRA_BOOKMARK_ID =
    "com.raywenderlich.placebook.EXTRA_BOOKMARK_ID"
intent.putExtra(EXTRA_BOOKMARK_ID, bookmarkId)
fun getLiveBookmark(bookmarkId: Long): LiveData<Bookmark> = 
  bookmarkDao.loadLiveBookmark(bookmarkId)
class BookmarkDetailsViewModel(application: Application) : AndroidViewModel(application) {
  private val bookmarkRepo = BookmarkRepo(getApplication())
}

Heads up... You’re accessing parts of this content for free, with some sections shown as ghxemzfat text.

Heads up... You’re accessing parts of this content for free, with some sections shown as fsfecknol text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
data class BookmarkDetailsView(
    var id: Long? = null,
    var name: String = "",
    var phone: String = "",
    var address: String = "",
    var notes: String = ""
) {

  fun getImage(context: Context) = id?.let {
      ImageUtils.loadBitmapFromFile(context, Bookmark.generateImageFilename(it))
  }
}

Adding the data tag

Now that you have created the BookmarkDetailsView class, you can add a variable to the activity_bookmark_details.xml file. Right after the <layout> tag add:

<data>
  <variable
    name="bookmarkDetailsView"
    type="com.raywenderlich.placebook.viewmodel.BookmarkDetailsViewModel.BookmarkDetailsView" />
</data>
android:text="@{bookmarkDetailsView.name}"
android:text="@{bookmarkDetailsView.notes}"
android:text="@{bookmarkDetailsView.phone}"
android:text="@{bookmarkDetailsView.address}"

Heads up... You’re accessing parts of this content for free, with some sections shown as jskopfwys text.

Heads up... You’re accessing parts of this content for free, with some sections shown as tdxujltyw text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Adding notes to the database

Before continuing, you need a way to store notes for a bookmark.

var notes: String = ""
data class Bookmark(
    @PrimaryKey(autoGenerate = true) var id: Long? = null,
    var placeId: String? = null,
    var name: String = "",
    var address: String = "",
    var latitude: Double = 0.0,
    var longitude: Double = 0.0,
    var phone: String = "",
    var notes: String = ""
)
@Database(entities = arrayOf(Bookmark::class), version = 2)
instance = Room.databaseBuilder(context.applicationContext,
    PlaceBookDatabase::class.java, "PlaceBook")
    .fallbackToDestructiveMigration()
    .build()

Heads up... You’re accessing parts of this content for free, with some sections shown as xxgitpjot text.

Heads up... You’re accessing parts of this content for free, with some sections shown as gfgucngys text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Bookmark view model

That’s all you need to support the revised Bookmark model in the database. Now you need to convert the database model to a view model.

private fun bookmarkToBookmarkView(bookmark: Bookmark): BookmarkDetailsView {
  return BookmarkDetailsView(
      bookmark.id,
      bookmark.name,
      bookmark.phone,
      bookmark.address,
      bookmark.notes
  )
}
private var bookmarkDetailsView: LiveData<BookmarkDetailsView>? = null
private fun mapBookmarkToBookmarkView(bookmarkId: Long) {
  val bookmark = bookmarkRepo.getLiveBookmark(bookmarkId)
  bookmarkDetailsView = Transformations.map(bookmark) { repoBookmark ->
    bookmarkToBookmarkView(repoBookmark)
  }
}
fun getBookmark(bookmarkId: Long): LiveData<BookmarkDetailsView>? {
  if (bookmarkDetailsView == null) {
    mapBookmarkToBookmarkView(bookmarkId)
  }
  return bookmarkDetailsView
}

Retrieving the bookmark view

You’re ready to add the code to retrieve the BookmarkDetailsView LiveData object in the View Activity.

Heads up... You’re accessing parts of this content for free, with some sections shown as rpdojggig text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lrxymtdag text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
private val bookmarkDetailsViewModel by 
    viewModels<BookmarkDetailsViewModel>()
private var bookmarkDetailsView:
    BookmarkDetailsViewModel.BookmarkDetailsView? = null
import androidx.activity.viewModels
private fun populateImageView() {
  bookmarkDetailsView?.let { bookmarkView ->
    val placeImage = bookmarkView.getImage(this)
    placeImage?.let {
      databinding.imageViewPlace.setImageBitmap(placeImage)
    }
  }
}

Using the intent data

When the user taps on the Info window for a bookmark on the maps Activity, it passes the bookmark ID to the details Activity.

private fun getIntentData() {
  // 1
  val bookmarkId = intent.getLongExtra(
      MapsActivity.Companion.EXTRA_BOOKMARK_ID, 0)  
  // 2    
  bookmarkDetailsViewModel.getBookmark(bookmarkId)?.observe(this, {
    // 3
    it?.let {
      bookmarkDetailsView = it
      // 4
      databinding.bookmarkDetailsView = it
      populateImageView()
    }
  })
}

Heads up... You’re accessing parts of this content for free, with some sections shown as dwhuphgyr text.

Heads up... You’re accessing parts of this content for free, with some sections shown as pkbartbux text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Finishing the detail activity

You’re ready to pull everything together by adding the following call to the end of onCreate() in BookmarkDetailsActivity.

getIntentData()

Saving changes

The only major feature left is to save the user’s edits. For that, you’ll add a checkmark Toolbar item to trigger the save.

<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=
    "com.raywenderlich.placebook.ui.BookmarkDetailsActivity">

  <item
      android:id="@+id/action_save"
      android:icon="@drawable/ic_action_done"
      android:title="Save"
      app:showAsAction="ifRoom"/>
</menu>

Heads up... You’re accessing parts of this content for free, with some sections shown as gkwevhdih text.

Heads up... You’re accessing parts of this content for free, with some sections shown as bwpupnwip text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
override fun onCreateOptionsMenu(menu: android.view.Menu): Boolean {
  menuInflater.inflate(R.menu.menu_bookmark_details, menu)
  return true
}
fun updateBookmark(bookmark: Bookmark) {
  bookmarkDao.updateBookmark(bookmark)
}

fun getBookmark(bookmarkId: Long): Bookmark {
  return bookmarkDao.loadBookmark(bookmarkId)
}
private fun bookmarkViewToBookmark(bookmarkView: BookmarkDetailsView): Bookmark? {
  val bookmark = bookmarkView.id?.let {
    bookmarkRepo.getBookmark(it)
  }
  if (bookmark != null) {
    bookmark.id = bookmarkView.id
    bookmark.name = bookmarkView.name
    bookmark.phone = bookmarkView.phone
    bookmark.address = bookmarkView.address
    bookmark.notes = bookmarkView.notes
  }
  return bookmark
}

Heads up... You’re accessing parts of this content for free, with some sections shown as gqboslgyh text.

Heads up... You’re accessing parts of this content for free, with some sections shown as rqmecvxej text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
fun updateBookmark(bookmarkView: BookmarkDetailsView) {
  // 1
  GlobalScope.launch {
    // 2
    val bookmark = bookmarkViewToBookmark(bookmarkView)
    // 3
    bookmark?.let { bookmarkRepo.updateBookmark(it) }
  }
}
private fun saveChanges() {
  val name = databinding.editTextName.text.toString()
  if (name.isEmpty()) {
    return
  }
  bookmarkDetailsView?.let { bookmarkView ->
    bookmarkView.name = databinding.editTextName.text.toString()
    bookmarkView.notes = databinding.editTextNotes.text.toString()
    bookmarkView.address = databinding.editTextAddress.text.toString()
    bookmarkView.phone = databinding.editTextPhone.text.toString()
    bookmarkDetailsViewModel.updateBookmark(bookmarkView)
  }
  finish()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
  R.id.action_save -> {
    saveChanges()
    true
  }
  else -> super.onOptionsItemSelected(item)
}

Heads up... You’re accessing parts of this content for free, with some sections shown as hrvilnnyf text.

Heads up... You’re accessing parts of this content for free, with some sections shown as qbhutcrig text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Key Points

Placebook is starting to take shape. In this chapter you learned:

Where to go from here?

Congratulations! You can now edit bookmarks, but there’s still more work to do. The next chapter wraps things up by adding some additional features and putting the finishing touches on the app.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as hzkezbnol text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now