├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── stacktex │ │ └── mobin │ │ └── search │ │ └── adapter │ │ ├── MainActivityInstrumentedTest.kt │ │ └── SampleActivityInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── stacktex │ │ │ └── mobin │ │ │ └── search │ │ │ ├── MainActivity.kt │ │ │ ├── adapter │ │ │ ├── DynamicSearchAdapter.kt │ │ │ ├── Helper.kt │ │ │ ├── SearchAdapter1.kt │ │ │ ├── SearchAdapter2.kt │ │ │ ├── SearchAdapter3.kt │ │ │ └── ViewHolder.kt │ │ │ ├── model │ │ │ ├── SearchModel1.kt │ │ │ ├── SearchModel2.kt │ │ │ └── SearchModel3.kt │ │ │ └── ui │ │ │ └── main │ │ │ ├── PageViewModel.kt │ │ │ ├── SampleActivity2.kt │ │ │ ├── SearchFragment1.kt │ │ │ ├── SearchFragment2.kt │ │ │ └── SectionsPagerAdapter.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── fragment_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── stacktex │ └── mobin │ └── search │ └── adapter │ └── ExampleUnitTest.kt ├── build.gradle └── settings.gradle /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mobin Munir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Dynamic-Search-Adapter 2 | An abstract recycler view adapter (One for all) that provides a search feature to unlimited number of extending adapters for their individual lists. 3 | 4 | ## Features 5 | - Performance: O(n) 6 | - Asynchronous 7 | - One Search adapter for every usage. 8 | - Accomodates Every list type which implements Searchable Interface. 9 | - Its not a fixed dependency to be included in your project to increase redundancy. 10 | - Its flexible to be converted in any library/SDK or modular form as per your requirement. 11 | - Modifications/Enhancements can be made as required. 12 | - Highly decoupled,optimized and clean code. 13 | - No Obfuscation Required (Proguard/Dexguard). 14 | - Complete Documentation. 15 | - UI tested (Espresso). 16 | - **It would be a part of your project while not implying any 3rd-party involvement.** 17 | 18 | ### How to use ? 19 | 20 | Just clone the project in Android Studio and run it. 21 | 22 | For details read more on [medium](https://android.jlelse.eu/search-on-recycler-view-android-e7661479481). 23 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "stacktex.mobin.search.adapter" 11 | minSdkVersion 21 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | androidExtensions { 24 | experimental = true 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'androidx.appcompat:appcompat:1.1.0-alpha05' 32 | implementation 'androidx.core:core-ktx:1.2.0-alpha01' 33 | implementation 'com.google.android.material:material:1.1.0-alpha07' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' 35 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha01' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'androidx.test:runner:1.2.0' 38 | // For activity rules 39 | androidTestImplementation 'androidx.test:rules:1.2.0' 40 | 41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 42 | 43 | // For recycler view actions 44 | androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0' 45 | 46 | // for fragment testing 47 | implementation 'androidx.fragment:fragment-testing:1.1.0-beta01' 48 | 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/stacktex/mobin/search/adapter/MainActivityInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import androidx.fragment.app.testing.launchFragmentInContainer 4 | import androidx.test.espresso.Espresso 5 | import androidx.test.espresso.action.ViewActions 6 | import androidx.test.espresso.matcher.RootMatchers 7 | import androidx.test.espresso.matcher.ViewMatchers 8 | import androidx.test.rule.ActivityTestRule 9 | import org.hamcrest.Matchers 10 | import org.junit.Rule 11 | import org.junit.Test 12 | import stacktex.mobin.search.MainActivity 13 | import stacktex.mobin.search.ui.main.SearchFragment1 14 | 15 | /** 16 | * Instrumented test, which will execute on an Android device. 17 | * 18 | * See [testing documentation](http://d.android.com/tools/testing). 19 | */ 20 | 21 | class MainActivityInstrumentedTest { 22 | @Rule 23 | @JvmField 24 | val activityRule = ActivityTestRule(MainActivity::class.java) 25 | 26 | @Test 27 | fun testFloatingButtonClick() { 28 | val mainActivity = activityRule.activity as MainActivity 29 | Espresso.onView(ViewMatchers.withId(R.id.fab)) 30 | .inRoot(RootMatchers.withDecorView(Matchers.`is`(mainActivity.window.decorView))) 31 | .perform(ViewActions.click()) 32 | 33 | 34 | } 35 | 36 | 37 | @Test 38 | fun testTab1Search() { 39 | launchFragmentInContainer { 40 | SearchFragment1.newInstance(0) 41 | } 42 | Espresso.onView(ViewMatchers.withId(R.id.searchV)).perform(ViewActions.typeText("abc")) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/androidTest/java/stacktex/mobin/search/adapter/SampleActivityInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import androidx.test.espresso.Espresso 4 | import androidx.test.espresso.contrib.RecyclerViewActions 5 | import androidx.test.espresso.matcher.RootMatchers 6 | import androidx.test.espresso.matcher.ViewMatchers 7 | import androidx.test.rule.ActivityTestRule 8 | import org.hamcrest.Matchers 9 | import org.junit.Rule 10 | import org.junit.Test 11 | import stacktex.mobin.search.ui.main.SampleActivity2 12 | 13 | class SampleActivityInstrumentedTest { 14 | @Rule 15 | @JvmField 16 | val activityRule = ActivityTestRule(SampleActivity2::class.java) 17 | 18 | 19 | @Test 20 | fun testSampleActivity() { 21 | val mainActivity = activityRule.activity as SampleActivity2 22 | Espresso.onView(ViewMatchers.withId(R.id.rv)) 23 | .inRoot(RootMatchers.withDecorView(Matchers.`is`(mainActivity.window.decorView))) 24 | .perform(RecyclerViewActions.scrollToPosition(50)) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | import stacktex.mobin.search.adapter.R 8 | import stacktex.mobin.search.ui.main.SampleActivity2 9 | import stacktex.mobin.search.ui.main.SectionsPagerAdapter 10 | 11 | class MainActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(R.layout.activity_main) 16 | val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager) 17 | viewPager.adapter = sectionsPagerAdapter 18 | tabs.setupWithViewPager(viewPager) 19 | fab.setOnClickListener { view -> 20 | startActivity(Intent(view.context, SampleActivity2::class.java)) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/adapter/DynamicSearchAdapter.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import android.widget.Filter 4 | import android.widget.Filterable 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | abstract class DynamicSearchAdapter(private val searchableList: MutableList) : 8 | RecyclerView.Adapter(), Filterable { 9 | 10 | // Single not-to-be-modified copy of original data in the list. 11 | private val originalList = ArrayList(searchableList) 12 | // a method-body to invoke when search returns nothing. It can be null. 13 | private var onNothingFound: (() -> Unit)? = null 14 | 15 | /** 16 | * Searches a specific item in the list and updates adapter. 17 | * if the search returns empty then onNothingFound callback is invoked if provided which can be used to update UI 18 | * @param s the search query or text. It can be null. 19 | * @param onNothingFound a method-body to invoke when search returns nothing. It can be null. 20 | */ 21 | fun search(s: String?, onNothingFound: (() -> Unit)?) { 22 | this.onNothingFound = onNothingFound 23 | filter.filter(s) 24 | 25 | } 26 | 27 | override fun getFilter(): Filter { 28 | return object : Filter() { 29 | private val filterResults = FilterResults() 30 | override fun performFiltering(constraint: CharSequence?): FilterResults { 31 | searchableList.clear() 32 | if (constraint.isNullOrBlank()) { 33 | searchableList.addAll(originalList) 34 | } else { 35 | val searchResults = originalList.filter { it.getSearchCriteria().contains(constraint) } 36 | searchableList.addAll(searchResults) 37 | } 38 | return filterResults.also { 39 | it.values = searchableList 40 | } 41 | } 42 | 43 | override fun publishResults(constraint: CharSequence?, results: FilterResults?) { 44 | // no need to use "results" filtered list provided by this method. 45 | if (searchableList.isNullOrEmpty()) 46 | onNothingFound?.invoke() 47 | notifyDataSetChanged() 48 | 49 | } 50 | } 51 | } 52 | 53 | interface Searchable { 54 | /** This method will allow to specify a search string to compare against 55 | your search this can be anything depending on your use case. 56 | */ 57 | fun getSearchCriteria(): String 58 | } 59 | 60 | 61 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/adapter/Helper.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import stacktex.mobin.search.model.SearchModel1 4 | import stacktex.mobin.search.model.SearchModel2 5 | import stacktex.mobin.search.model.SearchModel3 6 | 7 | //infix fun BaseSearchModel.createSearchable(searchContent: String): DynamicSearchAdapter.Searchable { 8 | // return object : DynamicSearchAdapter.Searchable { 9 | // override fun getSearchCriteria() = searchContent 10 | // } 11 | // 12 | //} 13 | 14 | fun MutableList.populateWithUUIDSM1(): MutableList { 15 | for (i in 0..50) 16 | add(SearchModel1(java.util.UUID.randomUUID().toString())) 17 | return this 18 | 19 | } 20 | 21 | fun MutableList.populateWithUUIDSM2(): MutableList { 22 | for (i in 0..50) 23 | add(SearchModel2(java.util.UUID.randomUUID().toString())) 24 | return this 25 | 26 | 27 | } 28 | 29 | fun MutableList.populateWithUUIDSM3(): MutableList { 30 | for (i in 0..50) 31 | add(SearchModel3(java.util.UUID.randomUUID().toString())) 32 | return this 33 | 34 | 35 | } 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/adapter/SearchAdapter1.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import android.view.ViewGroup 4 | import android.widget.TextView 5 | import stacktex.mobin.search.model.SearchModel1 6 | 7 | class SearchAdapter1(private val mutableList: MutableList) : 8 | DynamicSearchAdapter(mutableList) { 9 | 10 | 11 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 12 | val textView = TextView(parent.context) 13 | return ViewHolder(textView) 14 | } 15 | 16 | override fun getItemCount(): Int { 17 | return mutableList.count() 18 | } 19 | 20 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 21 | val tv = holder.containerView as TextView 22 | tv.text = mutableList[position].data 23 | 24 | } 25 | 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/adapter/SearchAdapter2.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import android.view.ViewGroup 4 | import android.widget.TextView 5 | import stacktex.mobin.search.model.SearchModel2 6 | 7 | class SearchAdapter2(private val mutableList: MutableList) : 8 | DynamicSearchAdapter(mutableList) { 9 | 10 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 11 | val textView = TextView(parent.context) 12 | return ViewHolder(textView) 13 | } 14 | 15 | override fun getItemCount(): Int { 16 | return mutableList.count() 17 | } 18 | 19 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 20 | val tv = holder.containerView as TextView 21 | tv.text = mutableList[position].data 22 | 23 | } 24 | 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/adapter/SearchAdapter3.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import android.view.ViewGroup 4 | import android.widget.TextView 5 | import stacktex.mobin.search.model.SearchModel3 6 | 7 | class SearchAdapter3(private val mutableList: MutableList) : 8 | DynamicSearchAdapter(mutableList) { 9 | 10 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 11 | val textView = TextView(parent.context) 12 | return ViewHolder(textView) 13 | } 14 | 15 | override fun getItemCount(): Int { 16 | return mutableList.count() 17 | } 18 | 19 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 20 | val tv = holder.containerView as TextView 21 | tv.text = mutableList[position].data 22 | 23 | } 24 | 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/adapter/ViewHolder.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.adapter 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import kotlinx.android.extensions.LayoutContainer 6 | 7 | class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/model/SearchModel1.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.model 2 | 3 | import stacktex.mobin.search.adapter.DynamicSearchAdapter 4 | 5 | class SearchModel1(val data: String) : DynamicSearchAdapter.Searchable { 6 | 7 | 8 | override fun getSearchCriteria(): String { 9 | return data 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/model/SearchModel2.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.model 2 | 3 | import stacktex.mobin.search.adapter.DynamicSearchAdapter 4 | 5 | data class SearchModel2(val data: String) : DynamicSearchAdapter.Searchable { 6 | override fun getSearchCriteria(): String { 7 | return data 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/model/SearchModel3.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.model 2 | 3 | import stacktex.mobin.search.adapter.DynamicSearchAdapter 4 | 5 | data class SearchModel3(val data: String) : DynamicSearchAdapter.Searchable { 6 | override fun getSearchCriteria(): String { 7 | return data 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/ui/main/PageViewModel.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.ui.main 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.Transformations 6 | import androidx.lifecycle.ViewModel 7 | 8 | class PageViewModel : ViewModel() { 9 | 10 | private val _index = MutableLiveData() 11 | val text: LiveData = Transformations.map(_index) { 12 | "Hello world from section: $it" 13 | } 14 | 15 | fun setIndex(index: Int) { 16 | _index.value = index 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/ui/main/SampleActivity2.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.ui.main 2 | 3 | import android.os.Bundle 4 | import android.widget.Toast 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.appcompat.widget.SearchView 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import kotlinx.android.synthetic.main.fragment_main.* 9 | import stacktex.mobin.search.adapter.R 10 | import stacktex.mobin.search.adapter.SearchAdapter3 11 | import stacktex.mobin.search.adapter.populateWithUUIDSM3 12 | import stacktex.mobin.search.model.SearchModel3 13 | 14 | class SampleActivity2 : AppCompatActivity(), SearchView.OnQueryTextListener { 15 | 16 | 17 | private lateinit var searchAdapter3: SearchAdapter3 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.fragment_main) 22 | rv.layoutManager = LinearLayoutManager(this) 23 | searchAdapter3 = SearchAdapter3(mutableListOf().populateWithUUIDSM3()) 24 | rv.adapter = searchAdapter3 25 | searchV.setOnQueryTextListener(this) 26 | 27 | } 28 | 29 | override fun onQueryTextChange(newText: String?): Boolean { 30 | search(newText) 31 | return true 32 | } 33 | 34 | override fun onQueryTextSubmit(query: String?): Boolean { 35 | search(query) 36 | return true 37 | } 38 | 39 | private fun search(s: String?) { 40 | searchAdapter3.search(s) { 41 | // update UI on nothing found 42 | Toast.makeText(this, "Nothing Found", Toast.LENGTH_SHORT).show() 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/ui/main/SearchFragment1.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.ui.main 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Toast 8 | import androidx.appcompat.widget.SearchView 9 | import androidx.fragment.app.Fragment 10 | import androidx.lifecycle.Observer 11 | import androidx.lifecycle.ViewModelProviders 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import kotlinx.android.synthetic.main.fragment_main.* 14 | import stacktex.mobin.search.adapter.R 15 | import stacktex.mobin.search.adapter.SearchAdapter1 16 | import stacktex.mobin.search.adapter.populateWithUUIDSM1 17 | import stacktex.mobin.search.model.SearchModel1 18 | 19 | /** 20 | * A placeholder fragment containing a simple view. 21 | */ 22 | class SearchFragment1 : Fragment(), SearchView.OnQueryTextListener { 23 | 24 | private lateinit var pageViewModel: PageViewModel 25 | private lateinit var searchAdapter1: SearchAdapter1 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | pageViewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply { 30 | setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1) 31 | } 32 | } 33 | 34 | override fun onCreateView( 35 | inflater: LayoutInflater, container: ViewGroup?, 36 | savedInstanceState: Bundle? 37 | ): View? { 38 | 39 | 40 | return inflater.inflate(R.layout.fragment_main, container, false) 41 | 42 | } 43 | 44 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 45 | rv.layoutManager = LinearLayoutManager(context) 46 | searchAdapter1 = SearchAdapter1(mutableListOf().populateWithUUIDSM1()) 47 | rv.adapter = searchAdapter1 48 | searchV.setOnQueryTextListener(this) 49 | 50 | pageViewModel.text.observe(this, Observer { 51 | section_label.text = it 52 | }) 53 | 54 | } 55 | 56 | override fun onQueryTextChange(newText: String?): Boolean { 57 | search(newText) 58 | return true 59 | } 60 | 61 | override fun onQueryTextSubmit(query: String?): Boolean { 62 | search(query) 63 | return true 64 | } 65 | 66 | private fun search(s: String?) { 67 | searchAdapter1.search(s) { 68 | // update UI on nothing found 69 | Toast.makeText(context, "Nothing Found", Toast.LENGTH_SHORT).show() 70 | } 71 | } 72 | 73 | companion object { 74 | /** 75 | * The fragment argument representing the section number for this 76 | * fragment. 77 | */ 78 | private const val ARG_SECTION_NUMBER = "section_number" 79 | 80 | /** 81 | * Returns a new instance of this fragment for the given section 82 | * number. 83 | */ 84 | @JvmStatic 85 | fun newInstance(sectionNumber: Int): SearchFragment1 { 86 | return SearchFragment1().apply { 87 | arguments = Bundle().apply { 88 | putInt(ARG_SECTION_NUMBER, sectionNumber) 89 | } 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/ui/main/SearchFragment2.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.ui.main 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Toast 8 | import androidx.appcompat.widget.SearchView 9 | import androidx.fragment.app.Fragment 10 | import androidx.lifecycle.Observer 11 | import androidx.lifecycle.ViewModelProviders 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import kotlinx.android.synthetic.main.fragment_main.* 14 | import stacktex.mobin.search.adapter.R 15 | import stacktex.mobin.search.adapter.SearchAdapter2 16 | import stacktex.mobin.search.adapter.populateWithUUIDSM2 17 | import stacktex.mobin.search.model.SearchModel2 18 | 19 | /** 20 | * A placeholder fragment containing a simple view. 21 | */ 22 | class SearchFragment2 : Fragment(), SearchView.OnQueryTextListener { 23 | 24 | private lateinit var pageViewModel: PageViewModel 25 | private lateinit var searchAdapter2: SearchAdapter2 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | pageViewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply { 30 | setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1) 31 | } 32 | } 33 | 34 | override fun onCreateView( 35 | inflater: LayoutInflater, container: ViewGroup?, 36 | savedInstanceState: Bundle? 37 | ): View? { 38 | return inflater.inflate(R.layout.fragment_main, container, false) 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | rv.layoutManager = LinearLayoutManager(context) 43 | searchAdapter2 = SearchAdapter2(mutableListOf().populateWithUUIDSM2()) 44 | rv.adapter = searchAdapter2 45 | searchV.setOnQueryTextListener(this) 46 | 47 | pageViewModel.text.observe(this, Observer { 48 | section_label.text = it 49 | }) 50 | } 51 | 52 | override fun onQueryTextChange(newText: String?): Boolean { 53 | search(newText) 54 | return true 55 | } 56 | 57 | override fun onQueryTextSubmit(query: String?): Boolean { 58 | search(query) 59 | return true 60 | } 61 | 62 | private fun search(s: String?) { 63 | searchAdapter2.search(s) { 64 | // update UI on nothing found 65 | Toast.makeText(context, "Nothing Found", Toast.LENGTH_SHORT).show() 66 | } 67 | } 68 | 69 | companion object { 70 | /** 71 | * The fragment argument representing the section number for this 72 | * fragment. 73 | */ 74 | private const val ARG_SECTION_NUMBER = "section_number" 75 | 76 | /** 77 | * Returns a new instance of this fragment for the given section 78 | * number. 79 | */ 80 | @JvmStatic 81 | fun newInstance(sectionNumber: Int): SearchFragment2 { 82 | return SearchFragment2().apply { 83 | arguments = Bundle().apply { 84 | putInt(ARG_SECTION_NUMBER, sectionNumber) 85 | } 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/stacktex/mobin/search/ui/main/SectionsPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package stacktex.mobin.search.ui.main 2 | 3 | import android.content.Context 4 | import androidx.fragment.app.Fragment 5 | import androidx.fragment.app.FragmentManager 6 | import androidx.fragment.app.FragmentPagerAdapter 7 | import stacktex.mobin.search.adapter.R 8 | 9 | private val TAB_TITLES = arrayOf( 10 | R.string.tab_text_1, 11 | R.string.tab_text_2 12 | ) 13 | 14 | /** 15 | * A [FragmentPagerAdapter] that returns a fragment corresponding to 16 | * one of the sections/tabs/pages. 17 | */ 18 | class SectionsPagerAdapter(private val context: Context, fm: FragmentManager) : 19 | FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 20 | 21 | override fun getItem(position: Int): Fragment { 22 | // getItem is called to instantiate the fragment for the given page. 23 | // Return a SearchFragment1 (defined as a static inner class below). 24 | 25 | return when (position) { 26 | 0 -> SearchFragment1.newInstance(position + 1) 27 | else -> SearchFragment2.newInstance(position + 1) 28 | } 29 | } 30 | 31 | override fun getPageTitle(position: Int): CharSequence? { 32 | return context.resources.getString(TAB_TITLES[position]) 33 | } 34 | 35 | override fun getCount(): Int { 36 | // Show 2 total pages. 37 | return 2 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 24 | 25 | 30 | 31 | 32 | 37 | 38 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 30 | 31 | 32 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmobin789/Android-Dynamic-Search-Adapter/71e5a04d8428b9c6344615909cdd8fb224251651/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 16dp 7 | 8dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android Dynamic Search Adapter 3 | Tab 1 4 | Tab 2 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |