Skip to content

Commit

Permalink
Add MVP searchview functionality with rxjava (#25)
Browse files Browse the repository at this point in the history
* Add mvp searchview functionality with rxjava

* Route Options searchview ui

* Addressing requested changes

* Adding MaxHeightListView commenting
  • Loading branch information
connorreinhold authored Jan 29, 2020
1 parent 22fbcff commit 170126b
Show file tree
Hide file tree
Showing 34 changed files with 855 additions and 89 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ build/
app/src/main/res/values/google_maps_api.xml
.DS_Store
google-services.json

# API keys
keys.properties
Binary file modified app/.DS_Store
Binary file not shown.
23 changes: 18 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

def keyFile = file('../keys.properties')
def keyProperties = new Properties()
keyProperties.load(new FileInputStream(keyFile))

android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
Expand All @@ -16,6 +20,7 @@ android {
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue 'string', "google_maps_key", keyProperties["googleMapApiKey"]
}
buildTypes {
release {
Expand All @@ -31,18 +36,26 @@ android {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.0.1'
implementation 'net.sourceforge.streamsupport:streamsupport:1.7.0'
implementation "com.squareup.moshi:moshi-adapters:1.8.0"
implementation "com.squareup.moshi:moshi-kotlin:1.8.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'

implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.google.android.libraries.places:places:2.1.0'

// rxjava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

}
14 changes: 13 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ithaca_transit_android_v2">

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

<application
android:allowBackup="true"
Expand All @@ -12,9 +13,20 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity android:name=".RouteOptionsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.ithaca_transit_android_v2.MainActivity" />
</activity>
<!-- google_maps_key loaded in build.gradle -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
Expand Down
152 changes: 120 additions & 32 deletions app/src/main/java/com/example/ithaca_transit_android_v2/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,50 +1,138 @@
package com.example.ithaca_transit_android_v2

import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import com.example.ithaca_transit_android_v2.models.Coordinate
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import com.example.ithaca_transit_android_v2.models.Location
import com.example.ithaca_transit_android_v2.states.*
import com.example.ithaca_transit_android_v2.ui_adapters.SearchViewAdapter
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import io.reactivex.Observable
import io.reactivex.ObservableEmitter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_toolbar_search.*

class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var disposable: Disposable
private var mSearchLocations: List<Location> = ArrayList()
private var mSearchAdapter: SearchViewAdapter? = null

class MainActivity : AppCompatActivity() {
override fun onMapReady(map: GoogleMap?) {
map!!.setOnMapClickListener { point ->
Log.i("qwerty", "map clicked")
search_view.clearFocus()
}
}

private fun createSearchObservable(): Observable<SearchState> {
val obs = Observable.create { emitter: ObservableEmitter<SearchState> ->
val watcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
}

override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

override fun onTextChanged(searchText: CharSequence?, p1: Int, p2: Int, p3: Int) {
if (searchText!!.isEmpty()) {
emitter.onNext(EmptyInitClickState())
} else {
emitter.onNext(InitSearchState(searchText.toString()))
}
}
}

search_input.addTextChangedListener(watcher)

search_input.setOnFocusChangeListener { view, hasFocus ->
if (hasFocus && (view as EditText).text.isEmpty()) {
// search input clicked on with no text
emitter.onNext(EmptyInitClickState())
} else if (hasFocus) {
// search input clicked on with text query
emitter.onNext(InitSearchState((view as EditText).text.toString()))
} else {
// search input not focused - display the un-expanded version
emitter.onNext(SearchLaunchState())
}
}

emitter.setCancellable {
search_input.removeTextChangedListener(watcher)
search_input.setOnFocusChangeListener(null)
}
}
return obs.startWith(SearchLaunchState())
}

private fun initSearchView() {
val observable = createSearchObservable()

disposable = observable
.observeOn(Schedulers.io())
.map { state ->
if (state is InitSearchState) {
val locations = NetworkUtils().getSearchedLocations(state.searchText)
InitLocationsSearchState(state.searchText, locations)
} else {
state
}
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ state ->
when (state) {
is SearchLaunchState -> {
search_empty_state.visibility = View.GONE
search_locations_state.visibility = View.GONE
}
is EmptyInitClickState -> {
search_locations_state.visibility = View.GONE
search_empty_state.visibility = View.VISIBLE
}
is InitLocationsSearchState -> {
search_empty_state.visibility = View.GONE
search_locations_state.visibility = View.VISIBLE
if (state.searchedLocations!!.size > 0 || search_input.text.isEmpty()) {
mSearchAdapter!!.swapItems(state.searchedLocations)
}
}
}
}, { error -> Log.e("An Error Occurred", error.toString()) })
}

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

// ======== FOR TESTING ONLY ========
val end = Coordinate(42.444971674516864, -76.48098092526197)
// val uid = "E4A0256E-5865-4E9F-8A5A-33747CAC7EBF"
val time = 1574292741.0
val destinationName = "Bill & Melinda Gates Hall"
val start = Coordinate(42.44717985041025, -76.48551732274225)
val arriveBy = false

// TODO (lesley): just for testing purposes - replace with rxjava
runBlocking {
val deferred = CoroutineScope(Dispatchers.IO).async {
NetworkUtils().getRouteOptions(start, end, time, arriveBy, destinationName)
}.await()

// Printing out [deferred] in log for testing
printLongLog(deferred.toString())
mSearchAdapter = SearchViewAdapter(this, mSearchLocations)
locations_list.adapter = mSearchAdapter
locations_list.setOnItemClickListener { parent, view, position, id ->
val destination = parent.getItemAtPosition(position) as Location

val intent = Intent(this, RouteOptionsActivity::class.java)
startActivity(intent)
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left)


}
initSearchView()
(map_fragment as SupportMapFragment).getMapAsync(this)
}

private fun printLongLog (s: String) {
val maxLogSize = 1000
val stringLength = s.length
if (stringLength != null) {
for (i in 0..stringLength / maxLogSize) {
val start = i * maxLogSize
var end = (i + 1) * maxLogSize
end = if (end > s.length) s.length else end
Log.v("returnBody", s.substring(start, end))
}
override fun onStop() {
super.onStop()
if (!disposable.isDisposed) {
disposable.dispose()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.example.ithaca_transit_android_v2

import android.util.Log
import com.example.ithaca_transit_android_v2.models.Coordinate
import com.example.ithaca_transit_android_v2.models.Location
import com.example.ithaca_transit_android_v2.models.RouteOptions
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types.newParameterizedType
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
Expand All @@ -23,24 +25,24 @@ class NetworkUtils {
val mediaType = ("application/json; charset=utf-8").toMediaType()

// Function that takes in query and returns list of Locations
fun getSearchedLocations(query: String): List<Location> {
fun getSearchedLocations(query: String): List<Location>? {
val json = JSONObject()
json.put("query", query)
val requestBody = json.toString().toRequestBody(mediaType)
val request: Request = Request.Builder()
.url(url + "search")
.url(url + "appleSearch")
.post(requestBody)
.build()

val body = client.newCall(request).execute().body?.string()

val type = newParameterizedType(List::class.java, Location::class.java)
val moshi = Moshi.Builder()
.add(LocationAdapter())
.add(KotlinJsonAdapterFactory())
.build()

val adapter: JsonAdapter<List<Location>> = moshi.adapter(type)
return adapter.fromJson(body) ?: emptyList()
return adapter.fromJson(body.toString())?: emptyList()
}

fun getAllBusStops(): List<Location> {
Expand Down
Loading

0 comments on commit 170126b

Please sign in to comment.