Tuesday, 3 September 2024

How to create custom RecyclerView with voice search in Android Kotlin?



Step 1) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\src\main\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DirectionNavigationDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


Step 2) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\src\main\java\com\example\directionnavigationdemo\BlogPost.kt

package com.example.directionnavigationdemo

//This is our data/model class
data class BlogPost(

var title: String,

var body: String,

var image: String,

var username: String // Author of blog post


) {

override fun toString(): String {
return "BlogPost(title='$title', image='$image', username='$username')"
}


}

Step 3) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\src\main\java\com\example\directionnavigationdemo\DataSource.kt


package com.example.directionnavigationdemo


//This is the dummy data source fie
class DataSource{

companion object{

fun createDataSet(): ArrayList<BlogPost>{
val list = ArrayList<BlogPost>()
list.add(
BlogPost(
"Congratulations!",
"You made it to the end of the course!\r\n\r\nNext we'll be building the REST API!",
"https://raw.githubusercontent.com/mitchtabian/Blog-Images/master/digital_ocean.png",
"Sally"
)
)
list.add(
BlogPost(
"Time to Build a Kotlin App!",
"The REST API course is complete. You can find the videos here: https://codingwithmitch.com/courses/build-a-rest-api/.",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/time_to_build_a_kotlin_app.png",
"mitch"
)
)

list.add(
BlogPost(
"Interviewing a Web Developer and YouTuber",
"Justin has been producing online courses for YouTube, Udemy, and his website CodingForEntrepreneurs.com for over 5 years.",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/coding_for_entrepreneurs.png",
"John"
)
)
list.add(
BlogPost(
"Freelance Android Developer (Vasiliy Zukanov)",
"Vasiliy has been a freelance android developer for several years. He also has some of the best android development courses I've had the pleasure of taking on Udemy.com.",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/freelance_android_dev_vasiliy_zukanov.png",
"Steven"
)
)
list.add(
BlogPost(
"Freelance Android Developer, Donn Felker",
"Freelancing as an Android developer with Donn Felker.\\r\\n\\r\\nDonn is also:\\r\\n\\r\\n1) Founder of caster.io\\r\\n\\r\\n2) Co-host of the fragmented podcast (fragmentedpodcast.com).",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/freelance_android_dev_donn_felker.png",
"Richelle"
)
)
list.add(
BlogPost(
"Work Life Balance for Software Developers",
"What kind of hobbies do software developers have? It sounds like many software developers don't have a lot of hobbies and choose to focus on work. Is that a good idea?",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/work_life_balance.png",
"Jessica"
)
)
list.add(
BlogPost(
"Full Stack Web Developer - Nicholas Olsen",
"In this podcast I interviewed the Fullsnack Developer (AKA Nicholas Olsen).\\r\\n\\r\\nNicholas is many things. What I mean by that is, he's good at many things.\\r\\n\\r\\n1. He’s an entrepreneur\\r\\n\\r\\n2. Web developer\\r\\n\\r\\n3. Artist\\r\\n\\r\\n4. Graphic designer\\r\\n\\r\\n5. Musician (drums)\\r\\n\\r\\n6. Professional BodyBuilder.",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/fullsnack_developer.png",
"Guy"
)
)
list.add(
BlogPost(
"Javascript Expert - Wes Bos",
"Interviewing a web developer, javascript expert, entrepreneur, freelancer, podcaster, and much more.",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/javascript_expert_wes_bos.png",
"Ruby"
)
)
list.add(
BlogPost(
"Senior Android Engineer - Kaushik Gopal",
"Kaushik Gopal is a Senior Android Engineer working in Silicon Valley.\r\n\r\nHe works as a Senior Staff engineer at Instacart.\r\n\r\nInstacart: https://www.instacart.com/",
"https://raw.githubusercontent.com/mitchtabian/Kotlin-RecyclerView-Example/json-data-source/app/src/main/res/drawable/senior_android_engineer_kaushik_gopal.png",
"mitch"
)
)
return list
}
}
}

Step 4) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\src\main\java\com\example\directionnavigationdemo\TopSpacingItemDecoration.kt

package com.example.directionnavigationdemo

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class TopSpacingItemDecoration(private val padding: Int) : RecyclerView.ItemDecoration() {

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
}
}


Step 5) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\src\main\java\com\example\directionnavigationdemo\BlogRecyclerAdapter.kt

package com.example.directionnavigationdemo

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions

//This is our recyclerview class
class BlogRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private val TAG: String = "AppDebug"

private var items: ArrayList<BlogPost> = ArrayList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return BlogViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_blog_list_item, parent, false))
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
is BlogViewHolder -> {
holder.bind(items.get(position))
}
}
}

override fun getItemCount(): Int {
return items.size
}

fun submitList(blogList: ArrayList<BlogPost>){
items.clear() //old list will clear here
items = blogList //new list data will assign
notifyDataSetChanged() //apply changes
}

class BlogViewHolder constructor(itemView: View): RecyclerView.ViewHolder(itemView){

val blog_image = itemView.findViewById<ImageView>(R.id.blog_image)
val blog_title = itemView.findViewById<TextView>(R.id.blog_title)
val blog_author = itemView.findViewById<TextView>(R.id.blog_author)

fun bind(blogPost: BlogPost){

val requestOptions = RequestOptions().placeholder(R.drawable.ic_launcher_background).error(R.drawable.ic_launcher_background)

Glide.with(itemView.context)
.applyDefaultRequestOptions(requestOptions)
.load(blogPost.image)
.into(blog_image)


blog_title.text = blogPost.title
blog_author.text = blogPost.username

}

}

}


Step 6) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\src\main\java\com\example\directionnavigationdemo\MainActivity.kt

package com.example.directionnavigationdemo

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.speech.RecognizerIntent
import android.widget.Toast
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.util.Locale
import java.util.Objects

//This is our main activity class

class MainActivity : AppCompatActivity() {

private lateinit var blogAdapter: BlogRecyclerAdapter
private lateinit var data: ArrayList<BlogPost>
private val REQUEST_CODE_SPEECH_INPUT = 11

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)


addDataSet()
}

private fun addDataSet(){
data = DataSource.createDataSet()
initRecyclerView(data)
blogAdapter.submitList(data)


val fab = findViewById<FloatingActionButton>(R.id.fab)

fab.setOnClickListener {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak to text")
voiceActivityResultLauncher.launch(intent)

try {
startActivityForResult(intent, REQUEST_CODE_SPEECH_INPUT)
} catch (e: Exception) {
// on below line we are displaying error message in toast
Toast.makeText(this@MainActivity, " " + e.message, Toast.LENGTH_SHORT).show()
}
}
}

private fun initRecyclerView(data: ArrayList<BlogPost>) {

val recycler_view = findViewById<RecyclerView>(R.id.recycler_view)
recycler_view.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
val topSpacingDecorator = TopSpacingItemDecoration(30)
addItemDecoration(topSpacingDecorator)
blogAdapter = BlogRecyclerAdapter()
adapter = blogAdapter
}
}


private var voiceActivityResultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult(), ActivityResultCallback {
if (it.resultCode === RESULT_OK) {

val res: ArrayList<String> = it.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) as ArrayList<String>

if (data.size>0){
filter(Objects.requireNonNull(res)[0])
}

Toast.makeText(this@MainActivity, "${ Objects.requireNonNull(res)[0] }", Toast.LENGTH_SHORT).show()
}
}
)

private fun filter(search: String) {
val filteredList: ArrayList<BlogPost> = ArrayList()
if (data.size>0)
for (item in data) {
if (item.title.lowercase().contains(search.lowercase())) {
filteredList.add(item)
}else if (item.username.lowercase().contains(search.lowercase())) {
filteredList.add(item)
}
}
// if (filteredList.isNotEmpty()){
blogAdapter.submitList(filteredList)

}


}

--------------------------------------------------------------------------------------------


Step 7) D:\Vinod\Projects\Test\DirectionNavigationDemo\build.gradle.kts
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
}

-----------------------------------------------------------------------------------------

Step 8) D:\Vinod\Projects\Test\DirectionNavigationDemo\app\build.gradle.kts

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
}

android {
namespace = "com.example.directionnavigationdemo"
compileSdk = 34

defaultConfig {
applicationId = "com.example.directionnavigationdemo"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.cardview)
implementation(libs.androidx.recyclerview)
implementation(libs.glide)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
--------------------------------------------------------------------------------------------


Step 9) D:\Vinod\Projects\Test\DirectionNavigationDemo\gradle\wrapper\gradle-wrapper.properties

#Tue Sep 03 17:51:29 IST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Step 10) D:\Vinod\Projects\Test\DirectionNavigationDemo\gradle\libs.versions.toml

[versions]
agp = "8.5.2"
kotlin = "1.9.0"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.9.0"
constraintlayout = "2.1.4"
cardview = "1.0.0"
recyclerview = "1.3.2"
glide = "5.0.0-rc01"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
jetbrains-kotlin-android-extensions = { id = "org.jetbrains.kotlin.android.extensions", version.ref = "kotlin" }

__________________________________________________________________________________________

Step 11)
D:\Vinod\Projects\Test\DirectionNavigationDemo\settings.gradle.kts

pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}

rootProject.name = "RecyclerviewDemo"
include(":app")
------------------------------------------------------------------------------------














No comments:

Post a Comment