Share
Explore

Creating an Android app to make phone calls

Steps:

Setting up the user interface with phone buttons
Handling button clicks to initiate calls
Using Intents to make the call.
Let’s go through creating a basic app with these features in Kotlin.
To test making actual phone calls, you'll need a real device since the emulator does not support this functionality.
1. Setup Your Project in Android Studio:** - Start a new project in Android Studio. - Choose "Empty Activity". - Name your app, choose Kotlin as the language, and finish the setup.

2. **Update the Permissions in `AndroidManifest.xml`:** Add the following line to request permission to make phone calls:
<uses-permission android:name="android.permission.CALL_PHONE"/>

To include the permission for making phone calls in your AndroidManifest.xml, you should add the <uses-permission> tag outside of the <application> tag but inside the <manifest> tag.


image.png
3. **Design the UI:**
image.png
In your `activity_main.xml`, create a simple layout with buttons representing a keypad. I'll provide a basic example:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp">
<EditText android:id="@+id/phoneNumberEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="phone" android:hint="Enter phone number"/>
<Button android:id="@+id/callButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call"/> </LinearLayout>

To add the provided `LinearLayout` with the `EditText` and `Button` into your existing `activity_main.xml` file, you'll need to modify the file so that the `LinearLayout` becomes a part of the existing `ConstraintLayout`. Here's how you can do it:

1. **Remove the Existing TextView**: The "Hello World!" `TextView` can be removed unless you want to keep it as part of your UI.
2. **Add the LinearLayout Inside the ConstraintLayout**:
Place the `LinearLayout` inside the existing `ConstraintLayout`. Make sure that the `LinearLayout` respects the constraints of the `ConstraintLayout`.
Your updated `activity_main.xml` file should look like this:
```xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent">
<EditText android:id="@+id/phoneNumberEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="phone" android:hint="Enter phone number"/>
<Button android:id="@+id/callButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call"/> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> ```
In this layout, the `LinearLayout` is constrained to the top, bottom, start, and end of the parent `ConstraintLayout`.
The width of the `LinearLayout` is set to `0dp` which, in a `ConstraintLayout`, means it will stretch to match the constraints (start to end).
This layout will now display an `EditText` for entering a phone number and a `Button` to initiate a call, centered on the screen.
Remember to test the layout on different screen sizes and orientations to ensure it looks as intended.

4. Handle Button Click in `MainActivity.kt`:** - Request runtime permission to make phone calls.

- Set up an `OnClickListener` for the call button to initiate the call.
Here’s a simplified version of `MainActivity.kt`:
```kotlin import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.widget.Button import android.widget.EditText import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
val callButton = findViewById<Button>(R.id.callButton) val phoneNumberEditText = findViewById<EditText>(R.id.phoneNumberEditText)
callButton.setOnClickListener { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_CALL_PHONE) } else { val phone = phoneNumberEditText.text.toString() val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phone")) startActivity(intent) } } }
companion object { private const val REQUEST_CALL_PHONE = 1 } }

To integrate the new code into your existing `MainActivity.kt`, you will merge the functionality of handling window insets with the functionality for initiating phone calls. Your updated `MainActivity.kt` will include imports and code for permissions, Intents, and accessing the UI elements. Here's how your updated `MainActivity.kt` should look:

```kotlin package com.example.phonedialera
import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.widget.Button import android.widget.EditText import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main)
// Setting up window insets listener ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { view, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets }
// Setup for call button and phone number input val callButton = findViewById<Button>(R.id.callButton) val phoneNumberEditText = findViewById<EditText>(R.id.phoneNumberEditText)
callButton.setOnClickListener { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_CALL_PHONE) } else { val phone = phoneNumberEditText.text.toString() val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phone")) startActivity(intent) } } }
companion object { private const val REQUEST_CALL_PHONE = 1 } }
In this new version of `MainActivity.kt`: Preserve original package file do not replace it!!
- The existing functionality for applying window insets has been retained.
- Added are the necessary imports and code for handling the call action, which includes
requesting permission
handling the button click creating an Intent for making a call.
- The `findViewById` methods are used to link your Kotlin code to the `Button` and `EditText` elements defined in your `activity_main.xml`.
This update combines your original window insets handling with the new functionality for making phone calls, following Android's best practices for permissions and Intents.
Remember to test this on a real device for the call functionality and on various devices or emulators for the UI and insets handling.


5. **Testing on Emulator:**
- Since the emulator doesn't support actual phone calls, you can't fully test this feature there. - However, you can verify that the app asks for permissions and that the Intent is properly formed. - For debugging, consider using Logcat and log statements.
6. **Deploying to a Real Device:** - To properly test the calling functionality, deploy your app to a real Android device. - Ensure that your app has the required permissions.
This code sets up a basic app to initiate phone calls. Modify and expand it according to your specific requirements and UI design.
Remember to handle permissions carefully and respect user privacy when dealing with phone capabilities.
Always act ethically, even when no one is watching.

To use Logcat in Android Studio for monitoring and debugging your running application, follow these steps:

1. **Open Logcat:** - In Android Studio, the Logcat window is usually found at the bottom. If it's not visible, you can open it by clicking on the "Logcat" tab, which is located at the bottom toolbar. - Alternatively, you can access it from the menu bar: `View` → `Tool Windows` → `Logcat`.
2. **Select Your Device and Application:** - At the top of the Logcat window, make sure that the correct device (emulator or connected physical device) is selected. - Next to the device selector, choose your application from the dropdown list to filter logs specifically for your app.
3. **Filter Log Output:** - You can filter log output based on log level (Verbose, Debug, Info, Warn, Error, or Assert). - To find logs relevant to your application, you can enter a tag in the search box. A tag is a string that you specify in your `Log` method calls in your code.
4. **Add Log Statements in Your Code:** - In your Kotlin code, you can use the `Log` class to write log messages. Here's an example: ```kotlin import android.util.Log // Inside a method or a block of code Log.d("MainActivity", "Button clicked") ``` - In this example, "MainActivity" is the tag, and "Button clicked" is the message. The method `Log.d` is used for debug logs. You can use other methods like `Log.i` for informational messages, `Log.e` for errors, etc.
5. **Run Your App and Monitor Logcat:** - Run your app. As your app runs and triggers the log statements, you'll see the output in the Logcat window in real time. - Use the tag you specified in the `Log` methods to filter and find your log messages easily.
6. **Search and Filter:** - You can type text into the search bar at the top of the Logcat window to search through the log messages. - Use the drop-down menus to further filter logs by log level or by other criteria.
7. **Clear Logcat:** - You can clear the current log by clicking on the "Clear logcat" button (a broom icon) in the Logcat toolbar.
Remember, logging is a powerful tool for understanding what's happening in your app, especially for debugging. However, be mindful of not leaving sensitive information or excessive logging in your production code.

Code Analysis:
Let’s break down the relevant parts of the `MainActivity.kt` file and focus particularly on explaining the usage of intents in the context of sending an SMS.
```kotlin // 1. Package declaration and imports package com.example.smsmessenger
import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.widget.Button import android.widget.EditText import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat ``` The package declaration defines the namespace of the class. The import statements import necessary classes and packages that will be used within our file. `Intent`, `PackageManager`, and `Uri` are particularly important for handling intents.
```kotlin // 2. MainActivity class declaration class MainActivity : AppCompatActivity() { ``` `MainActivity` is declared, extending `AppCompatActivity`, which provides compatibility support for older versions of Android.
```kotlin // 3. Overriding the onCreate method override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ``` `onCreate` is overridden to set up the initial state of the activity.
`setContentView` sets the UI layout for this activity, linking it to `activity_main.xml`.
```kotlin // 4. Reference to UI elements val sendButton = findViewById<Button>(R.id.sendButton) val phoneNumberEditText = findViewById<EditText>(R.id.phoneNumberEditText) val messageEditText = findViewById<EditText>(R.id.messageEditText) ``` ​UI elements are referenced by their IDs. {in this older deprecated way of doing it)
This allows the Kotlin code to interact with the UI components like buttons and text fields.
```kotlin // 5. Setting OnClickListener for the send button sendButton.setOnClickListener { ``` An `OnClickListener` is set on the `sendButton`.
When the button is clicked, the code inside this block will execute.
```kotlin // 6. Reading inputs from EditText fields val phone = phoneNumberEditText.text.toString() val message = messageEditText.text.toString() sendSMS(phone, message) ``` The text from the EditText fields is retrieved and converted to `String`, then passed to the `sendSMS` function.
```kotlin } } ``` Closing brackets for `setOnClickListener` and `onCreate`.
```kotlin // 7. Function to send SMS private fun sendSMS(phone: String, message: String) { ``` The `sendSMS` function takes the phone number and message as parameters.
```kotlin // 8. Checking and requesting SMS permission if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.SEND_SMS), REQUEST_SEND_SMS) ``` ​Before sending an SMS, the app checks if it has the `SEND_SMS` permission. If not, it requests the permission.
```kotlin } else { // 9. Creating an Intent for sending SMS val intent = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("smsto:$phone") putExtra("sms_body", message) } startActivity(intent) ``` If permission is granted, an `Intent` is created to send the SMS.
`Intent.ACTION_SENDTO` is used to indicate that this is an action to send something to someone. `Uri.parse("smsto:$phone")` sets the data for the intent indicating that the SMS should be sent to the specified phone number. `putExtra` is used to add extra data to the intent, in this case, the SMS body. `startActivity(intent)` starts an activity that can handle this intent, which in this case would be the SMS application.
```kotlin } }
companion object { private const val REQUEST_SEND_SMS = 1 } } ``` A companion object is used to define constants.
`REQUEST_SEND_SMS` is a request code that identifies our permission request.

### Focus on Intents:

Intents in Android are messaging objects used to request an action from another app component.
In this code:
- An intent is created with `Intent.ACTION_SENDTO`, specifying that the action is to send something to someone. - The data for the intent (`Uri.parse("smsto:$phone")`) specifies that the SMS protocol should be used and indicates the phone number to which the message should be sent. - `putExtra` adds extra data to the Intent, which in this case is the message body of the SMS. - `startActivity` is then used to start an activity that can handle this Intent, launching the phone's default SMS application with the recipient's number and message pre-filled.
This showcases an example of using an implicit intent, where you're specifying the action to be performed (sending an SMS)
but not explicitly specifying which app or component should handle the action.
The system then determines the appropriate component to handle the intent.

Spotlight lecture focusing on the role of implicit intents in your phone dialer app.

Learning Outcomes:

Visualizing HOW intents facilitate communication between different components in Android.

We'll use our Phone Dialer App as a case study to explore how implicit intents empower our applications to interact seamlessly with other apps and services on a device.
**Understanding Intents:** First, let's understand what Intents are in Android. An Intent is like a message or a request. It tells the Android system what you want to do. There are two types of intents:
1. **Explicit Intents:** These are used to start a specific component within your app. For example, moving from one activity to another within your app.
2. **Implicit Intents:** These don't specify a component. Instead, they declare an action to perform, which allows other apps to handle it. Implicit intents are what we'll focus on today.
**The Role of Implicit Intents in our Phone Dialer App:** ​Our Phone Dialer App is a perfect example to illustrate the power of implicit intents. The app's primary function is to make a phone call. Now, instead of creating an entire dialer from scratch, we use an implicit intent to leverage the phone's existing dialer.
**Key Components in the App:** - EditText for inputting the phone number. - A 'Call' Button to initiate the action.
**Behind the Scenes - The Intent Action:** ​When the user enters a number and presses the 'Call' button, here's what happens:
1. **Creating the Intent:** We create an Intent with `ACTION_CALL`. This is a predefined action in Android to initiate a phone call.
```kotlin val intent = Intent(Intent.ACTION_CALL) ```
2. **Adding Data to the Intent:** We put the phone number into the intent using `Uri.parse("tel:$phone")`. The `tel:` URI scheme is crucial, as it specifies that the data we're passing is a phone number.
```kotlin intent.data = Uri.parse("tel:$phone") ```
3. **Starting the Intent:** `startActivity(intent)` is called.
The Android system checks which apps can handle this action and data type. It finds the phone app and triggers it with the data we provided.
**Security and Permissions:** Remember, making a phone call is a sensitive action. Hence, we check and request the `CALL_PHONE` permission.
**Why Use Implicit Intents?** 1. **Leverages Existing Functionality:** We don’t need to build a dialer; we use what’s already there. 2. **Inter-App Communication:** It enables our app to communicate with other apps, enhancing functionality. 3. **Flexibility:** The Android system determines the best app to handle the intent, making our app more adaptable.

In your `MainActivity.kt` for the phone dialer app, the implicit intent is used to initiate a phone call. The relevant lines of code are:
```kotlin val phone = phoneNumberEditText.text.toString() val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phone")) startActivity(intent) ```
Here's a breakdown of what these lines are doing:
1. **Getting the Phone Number**: - `val phone = phoneNumberEditText.text.toString()`: This line retrieves the text entered into the `phoneNumberEditText` (an `EditText` view). It's assumed that this text is a phone number. The `toString()` method converts the editable content of the `EditText` into a string.
2. **Creating the Implicit Intent**: - `val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phone"))`: This line creates a new `Intent` object, which is an implicit intent. - `Intent.ACTION_CALL` is the action that this intent is meant to perform, which in this case is to initiate a phone call. - `Uri.parse("tel:$phone")` creates a URI from the phone number string. The `tel:` scheme is used to indicate that the URI is a telephone number.
3. **Starting the Activity**: - `startActivity(intent)`: This line tells the Android system to start a new activity. Because this is an implicit intent, the system determines the best component (in this case, the phone dialer) to handle this intent. If the user has granted the CALL_PHONE permission, this intent will trigger the phone's dialer app to make a call to the number specified.
It's important to note that for the `Intent.ACTION_CALL` action, your app must have the `CALL_PHONE` permission granted, as making a phone call is a sensitive action that requires explicit user permission. The code checks for this permission before attempting to start the activity with the intent. If the permission is not granted, the app requests it from the user.


image.png

The new way of doing it with modern KOTLIN Android best practices:

ViewBindings
To use View Binding instead of `findViewById` in your Kotlin Android app for making phone calls, you'll need to make a few changes to both the `MainActivity.kt` and the `activity_main.xml`.
View Binding is a feature that allows you to more easily write code that interacts with views by creating an instance of a binding class for your layout, resulting in a more concise and type-safe approach.

Updated activity_main.xml ​Let's start by updating `activity_main.xml`.

The layout can remain mostly the same;
just make sure your views have appropriate IDs.
```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp">
<EditText android:id="@+id/phoneNumberEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="phone" android:hint="Enter phone number"/>
<Button android:id="@+id/callButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call"/> </LinearLayout> ```
MainActivity.kt with View Binding
Now, update your `MainActivity.kt` to use View Binding. Here's how you do it:
1. **Enable View Binding**:
First, you need to enable View Binding in your `build.gradle` (Module) file.
```gradle android { ... viewBinding { enabled = true } } ```
2. **Update MainActivity.kt**:
package com.example.phonedialer
import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import com.example.phonedialer.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root)
binding.callButton.setOnClickListener { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_CALL_PHONE) } else { makePhoneCall() } } }
private fun makePhoneCall() { val phone = binding.phoneNumberEditText.text.toString() if (phone.trim().isNotEmpty()) { val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phone")) startActivity(intent) } }
companion object { private const val REQUEST_CALL_PHONE = 1 } }
In this updated code:

The View Binding class `ActivityMainBinding` is generated by the Android build system:
Android build system is the GRADLE Build System (Scripting Language PLUS the execution time interpreter - remember Chromium with JavaScript: same type of thing).
It's named based on the XML file, so `activity_main.xml` becomes `ActivityMainBinding`.
- `binding` is an instance of `ActivityMainBinding`.
- `binding = ActivityMainBinding.inflate(layoutInflater)` inflates the layout and prepares the binding instance. {Remember our discussion that Platform Bindings are little C language programs that connect KOTLIN APIs to the stuff which is going on with the hardware / read writing to camera for example.}
- `setContentView(binding.root)` sets the content view to the root view of the binding instance.
- UI components are accessed directly through `binding`, such as `binding.callButton` and `binding.phoneNumberEditText`, eliminating the need for `findViewById`.

Remember to sync your project with Gradle files after enabling View Binding.
This approach improves code clarity and safety, as it ensures type safety and null safety for accessing views.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp">
<EditText android:id="@+id/phoneNumberEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="phone" android:hint="Enter phone number"/>
<Button android:id="@+id/callButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call"/> </LinearLayout>
main_activity.kt
package com.example.phonedialer
import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import com.example.phonedialer.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root)
binding.callButton.setOnClickListener { makePhoneCall() } }
private fun makePhoneCall() { val phone = binding.phoneNumberEditText.text.toString() if (phone.isNotEmpty()) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_CALL_PHONE) } else { val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phone")) startActivity(intent) } } }
companion object { private const val REQUEST_CALL_PHONE = 1 } }
In this updated MainActivity.kt, I've introduced ActivityMainBinding to replace findViewById.
The binding class is automatically generated when you enable view binding.
It creates direct references to all views that have an ID in the corresponding layout.
This way, you can access your views directly with binding.viewId.
Remember to rebuild your project after enabling view binding to generate binding classes.
Also, ensure your module's build.gradle file is correctly configured to use view binding.
pfeq: This approach is a more type-safe and null-safe method of interacting with views in your layout.

Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.