Building a Kotlin Mobile App with the Salesforce SDK: Editing and Creating Data
Michael Bogan
Posted on March 4, 2022
Here in Part Two of our three-part series, we continue to build on the fundamentals of Android development with the Salesforce Mobile SDK. In Part One, we covered project setup and creating a layout that fetches data from Salesforce, using Kotlin as our programming language of choice.
Before we continue towards developing a complete mobile synchronization strategy, we’ll first build out our mobile app to allow editing and creating data on our Salesforce org.
Editing data
With the view established, let’s see how we can edit the data. Ideally, we want to maintain the same list format, but make it such that if a user taps on a name, they can edit it. Those changes should then be sent back to Salesforce.
To accomplish this, we need to do a few things. First, we need to override the default list behavior to make items editable. Then, after a record edit is complete, we need to make a call to the Salesforce API to update the data.
As before, let’s start with the layout. Create a file in app/res/layout
called broker_item.xml
and paste these lines into it:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:id="@+id/broker_field"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Next, create a file in app/java/com.example.sfdc
called BrokerListAdapter.kt
, and paste this into it:
package com.example.sfdc
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import com.salesforce.androidsdk.rest.BrokerItemBinding
import com.salesforce.androidsdk.rest.ApiVersionStrings
import com.salesforce.androidsdk.rest.RestClient
import com.salesforce.androidsdk.rest.RestRequest
import com.salesforce.androidsdk.rest.RestResponse
class BrokerListAdapter : ArrayAdapter<String> {
internal var context: Context
Private lateinit var brokerItemBinding: BrokerItemBinding
private var client: RestClient? = null
private val nameToId: MutableMap<String, String> = mutableMapOf()
constructor(context: Context) : super(context, R.layout.broker_item, ArrayList<String>(0)) {
this.context = context;
}
fun setClient(client: RestClient?) {
this.client = client
}
fun map(name: String, id: String) {
this.nameToId.put(name, id)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val inflater = context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
brokerItemBinding = BrokerItemBinding.inflate(inflater)
val rowView: View = inflater.inflate(R.layout.broker_item, parent, false)
val brokerField = brokerItemBinding.brokerField
var item = getItem(position)
var brokerId = nameToId.get(item)
val fields: MutableMap<String, String> = mutableMapOf()
brokerField.setText(item)
brokerField.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
item = brokerField.text.toString()
fields.put("Name", item!!)
val restRequest = RestRequest.getRequestForUpdate(ApiVersionStrings.getVersionNumber(context), "Broker__c", brokerId,
fields as Map<String, Any>?
)
client?.sendAsync(restRequest, object : RestClient.AsyncRequestCallback {
override fun onSuccess(request: RestRequest, result: RestResponse) {}
override fun onError(exception: Exception) {}
})
}
})
return rowView
}
}
In the Android ecosystem, an adapter defines the functionality of a UI element. Here, we’ve created a new adapter that overrides certain behaviors of ArrayAdapter
. We’ve added a function called setClient
, which reuses the same Salesforce API client that we need to populate the list over in MainActivity
. The map
function associates a broker’s name with their custom object ID—the reason why will be apparent soon.
getView
is where the bulk of the activity happens. The most important line is the one dealing with RestRequest.getRequestForUpdate
. The client calls this to update Salesforce data; it requires the name of the custom object, its ID, and the value to replace (in this case, the name). This event occurs when afterTextChanged
fires; that is, after the user has finished updating the broker’s name. In a real production environment, you need to check the status codes for potential errors from the API, but for the sake of brevity, we’ve omitted any type of response checking.
In MainActivity
, we need to make use of this adapter. First, at the top of the class definition, change listAdapter
to be a BrokerListAdapter
instead of an ArrayAdapter
, like so:
class MainActivity : SalesforceActivity() {
private var client: RestClient? = null
private var listAdapter: ArrayAdapter<String>? = null
private lateinit var listAdapter: BrokerListAdapter
...
The lateinit
modifier allows you to initialize a non-null type outside of the constructor. Next, replace the two onResume
functions with these:
override fun onResume() {
// Hide everything until we are logged in
mainViewBinding.root.visibility = View.INVISIBLE
// Create list adapter
listAdapter = BrokerListAdapter(this)
mainViewBinding.brokersList.adapter = listAdapter
super.onResume()
}
override fun onResume(client: RestClient) {
// Keeping reference to rest client
this.client = client
listAdapter.setClient(client)
// Show everything
mainViewBinding.root.visibility = View.VISIBLE
sendRequest("SELECT Name, Id FROM Broker__c")
}
Lastly, we need to keep track of the broker’s real name along with their record Id. To do that, we can simply store it in the dictionary the BrokerListAdapter
maintains. In sendRequest
, replace the for loop there with this one:
for (i in 0..records.length() - 1) {
listAdapter.add(records.getJSONObject(i).getString("Name"))
listAdapter.map(records.getJSONObject(i).getString("Name"), records.getJSONObject(i).getString("Id"))
}
Go ahead and launch the app, and your list items will be editable. Make an edit, and then get ready, because things are about to get wild. Go back to your scratch org, and click on the Brokers tab in the Dreamforce app. Your edits should be reflected here on the Salesforce platform!
Adding data
We can edit records, but what if we need to add a new broker? The format for this is pretty similar to the logic we used for fetching and editing records. Let’s quickly go through the steps.
Open up main.xml and paste these lines right before the ListView
:
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<Button
android:id="@+id/add_broker"
android:layout_width="0dp"
android:layout_height="47dp"
android:onClick="onAddBrokerClick"
android:text="Add broker"
android:background="?android:colorPrimary"
android:textColor="?attr/sfColorSecondary"
android:layout_gravity="center"
android:layout_weight="1"
android:layout_marginEnd="10dp"/>
</LinearLayout>
Here, we’ve added a button that will call a function called onAddBrokerClick
whenever it is pressed. In MainActivity
, we’ll define that method:
fun onAddBrokerClick(v: View) {
listAdapter.add("New Broker")
var fields: Map<String, String> = mapOf("name" to "New Broker",
"Title__c" to "Junior Broker",
"Phone__c" to "555-555-1234",
"Mobile_Phone__c" to "555-555-1234",
"Email__c" to "todo@salesforce.com",
"Picture__c" to "https://cdn.iconscout.com/icon/free/png-256/salesforce-282298.png")
val restRequest = RestRequest.getRequestForUpsert(ApiVersionStrings.getVersionNumber(this), "Broker__c", "Id", null, fields)
client?.sendAsync(restRequest, object : AsyncRequestCallback {
override fun onSuccess(request: RestRequest, result: RestResponse) {}
override fun onError(exception: Exception) {}
})
}
Yes, that’s it! Those fields we’ve defined relate directly to the custom object. By setting Id
to null, we’re telling the API that this is a new record. If you add a new row, edit it, and then head back to your scratch org, you’ll see the new data appear online.
To Be Continued…
Building on top of what we accomplished last time, in this post, we were able to edit and add broker data in our mobile app, and then see it reflected in Salesforce. Pretty neat!
However, this is only a one-way data change—from the app to the org. What about sending data from the org to the app? In addition, how can we handle network issues that are unique to mobile phones, such as connectivity issues, or multiple people changing fields at the same time? We’ll address these very real concerns in our concluding post!
Posted on March 4, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 23, 2024