From 15ffb44d531f3cf122a343c07769f935eadea81a Mon Sep 17 00:00:00 2001 From: hmalik144 Date: Thu, 5 Dec 2019 23:47:46 +1100 Subject: [PATCH] New commit with added features: - implement retrofit and rxjava --- .idea/misc.xml | 2 +- app/build.gradle | 7 +- .../h_mal/myapplication/MainActivityTest.kt | 48 +----- app/src/main/AndroidManifest.xml | 2 +- .../h_mal/myapplication/Api/GetData.kt | 11 ++ .../h_mal/myapplication/MainActivity.kt | 121 --------------- .../model/{JsonObject.kt => Repo.kt} | 10 +- .../myapplication/{ => ui}/ListViewAdapter.kt | 56 +++---- .../h_mal/myapplication/ui/MainActivity.kt | 144 ++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 39 ++++- app/src/main/res/layout/repo_list_item.xml | 3 +- 11 files changed, 236 insertions(+), 207 deletions(-) create mode 100644 app/src/main/java/com/example/h_mal/myapplication/Api/GetData.kt delete mode 100644 app/src/main/java/com/example/h_mal/myapplication/MainActivity.kt rename app/src/main/java/com/example/h_mal/myapplication/model/{JsonObject.kt => Repo.kt} (50%) rename app/src/main/java/com/example/h_mal/myapplication/{ => ui}/ListViewAdapter.kt (78%) create mode 100644 app/src/main/java/com/example/h_mal/myapplication/ui/MainActivity.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index d5727af..3484bf1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 56806ce..c8f3947 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,8 +30,11 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - implementation 'com.squareup.okhttp3:okhttp:4.2.1' - implementation 'com.google.code.gson:gson:2.8.5' androidTestImplementation 'com.android.support.test:rules:1.0.2' implementation 'com.android.support:cardview-v7:28.0.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.3.0' + implementation 'com.squareup.retrofit2:retrofit:2.3.0' + implementation 'io.reactivex.rxjava2:rxjava:2.1.9' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' } diff --git a/app/src/androidTest/java/com/example/h_mal/myapplication/MainActivityTest.kt b/app/src/androidTest/java/com/example/h_mal/myapplication/MainActivityTest.kt index 5eb92e6..ffe12c7 100644 --- a/app/src/androidTest/java/com/example/h_mal/myapplication/MainActivityTest.kt +++ b/app/src/androidTest/java/com/example/h_mal/myapplication/MainActivityTest.kt @@ -2,28 +2,22 @@ package com.example.h_mal.myapplication import android.support.test.espresso.Espresso -import android.support.test.espresso.Espresso.* -import android.support.test.espresso.action.ViewActions.click -import android.support.test.espresso.matcher.ViewMatchers.withId +import android.support.test.espresso.Espresso.onView +import android.support.test.espresso.assertion.ViewAssertions.matches +import android.support.test.espresso.matcher.ViewMatchers.* import android.support.test.filters.LargeTest import android.support.test.rule.ActivityTestRule import android.support.test.runner.AndroidJUnit4 import android.view.View import android.view.ViewGroup +import com.example.h_mal.myapplication.ui.MainActivity import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf -import org.hamcrest.Matchers.anything import org.hamcrest.TypeSafeMatcher import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import android.support.test.espresso.UiController -import android.support.test.orchestrator.junit.BundleJUnitUtils.getDescription -import android.support.test.espresso.ViewAction -import android.support.test.espresso.action.ViewActions.swipeDown -import android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast - @LargeTest @RunWith(AndroidJUnit4::class) @@ -36,24 +30,6 @@ class MainActivityTest { @Test fun mainActivityTest() { - onIdle() - - - val relativeLayout = onData(anything()) - .inAdapterView( - allOf( - withId(R.id.list_view), - childAtPosition( - withId(R.id.swipe_refresh), - 0 - ) - ) - ) - .atPosition(0) - relativeLayout.perform(click()) - - onView(withId(R.id.swipe_refresh)) - .perform(withCustomConstraints(swipeDown(), isDisplayingAtLeast(85))) } private fun childAtPosition( @@ -73,20 +49,4 @@ class MainActivityTest { } } } - - fun withCustomConstraints(action: ViewAction, constraints: Matcher): ViewAction { - return object : ViewAction { - override fun getConstraints(): Matcher { - return constraints - } - - override fun getDescription(): String { - return action.description - } - - override fun perform(uiController: UiController, view: View) { - action.perform(uiController, view) - } - } - } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b7dc53e..64c7128 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/app/src/main/java/com/example/h_mal/myapplication/Api/GetData.kt b/app/src/main/java/com/example/h_mal/myapplication/Api/GetData.kt new file mode 100644 index 0000000..f6cbb85 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/myapplication/Api/GetData.kt @@ -0,0 +1,11 @@ +package com.example.h_mal.myapplication.Api + +import com.example.h_mal.myapplication.model.Repo +import io.reactivex.Observable +import retrofit2.http.GET + +interface GetData{ + + @GET("repos") + fun getData() : Observable> +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/myapplication/MainActivity.kt b/app/src/main/java/com/example/h_mal/myapplication/MainActivity.kt deleted file mode 100644 index 5ed2182..0000000 --- a/app/src/main/java/com/example/h_mal/myapplication/MainActivity.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.example.h_mal.myapplication - -import android.content.Context -import android.support.v7.app.AppCompatActivity -import android.os.Bundle -import android.support.v4.widget.SwipeRefreshLayout -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.widget.SearchView -import com.example.h_mal.myapplication.model.JsonObject -import com.google.gson.Gson -import kotlinx.android.synthetic.main.activity_main.* -import okhttp3.* -import java.io.IOException - - -class MainActivity : AppCompatActivity() { - lateinit var searchView: SearchView - - val urlString = "https://api.github.com/orgs/square/repos" - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - //begin populating list - executeTask() - - //set a listener for the swipe to refresh - swipe_refresh.setOnRefreshListener(swipeRefreshListener) - } - - //implement search interface in the menu - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - //inflate custom menu as our menu - menuInflater.inflate(R.menu.menu, menu) - //extract searchview - val searchItem = menu?.findItem(R.id.search) - //set searchview globally - searchView = searchItem?.actionView as SearchView - - return true - } - - val swipeRefreshListener = SwipeRefreshLayout.OnRefreshListener{ - //populate list when pulling to refresh - executeTask() - } - - fun executeTask(){ - //clear list before populating - list_view.adapter = null - - //create url from url string urlstring - val url = Request.Builder().url(urlString).build() - //create a okhttpclient for a get request from url - val client = OkHttpClient() - - //call the url and retrieve its callback - client.newCall(url).enqueue(object: Callback { - //failure of retrieval callback - override fun onFailure(call: Call, e: IOException) { - //print error to log - System.err.println(e) - //if swipe refresh is refreshing then stop - swipe_refresh.isRefreshing = false - //list is empty - } - - //successful retrieval callback - override fun onResponse(call: Call, response: Response) { - //get the JSON from the body - val urlResponse = response.body?.string() - - //print the response to the logs - println("response = $urlResponse") - - //create gson object - val gson = Gson() - //create a mutable list of objects extracted from the json text - val objectList = gson.fromJson(urlResponse, Array::class.java).asList().toMutableList() - - if (objectList.size >0){ - //update the ui - runOnUiThread{ - //custom list view adapter created - val adapterLV = ListViewAdapter(baseContext, objectList) - //apply adapter to listview - list_view.adapter = adapterLV - //if swipe refresh is refreshing then stop - swipe_refresh.isRefreshing = false - - //search view has its query change listener applied - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{ - - override fun onQueryTextSubmit(query: String?): Boolean { - - return true - } - - //as the test is changed the list is filtered - override fun onQueryTextChange(newText: String?): Boolean { - //filter list function - adapterLV.filter.filter(newText) - - return true - } - }) - } - }else{ - //list is empty - } - - } - }) - } - - -} diff --git a/app/src/main/java/com/example/h_mal/myapplication/model/JsonObject.kt b/app/src/main/java/com/example/h_mal/myapplication/model/Repo.kt similarity index 50% rename from app/src/main/java/com/example/h_mal/myapplication/model/JsonObject.kt rename to app/src/main/java/com/example/h_mal/myapplication/model/Repo.kt index 782fad6..bb11b0f 100644 --- a/app/src/main/java/com/example/h_mal/myapplication/model/JsonObject.kt +++ b/app/src/main/java/com/example/h_mal/myapplication/model/Repo.kt @@ -2,15 +2,9 @@ package com.example.h_mal.myapplication.model import com.google.gson.annotations.SerializedName -data class JsonObject( - @SerializedName("name") +data class Repo( var name: String? = null, - @SerializedName("description") var description : String? = null, - @SerializedName("language") var language : String? = null, - @SerializedName("created_at") var date : String? = null, - @SerializedName("html_url") - var repoUrlString : String? = null -) + var html_url : String? = null) \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/myapplication/ListViewAdapter.kt b/app/src/main/java/com/example/h_mal/myapplication/ui/ListViewAdapter.kt similarity index 78% rename from app/src/main/java/com/example/h_mal/myapplication/ListViewAdapter.kt rename to app/src/main/java/com/example/h_mal/myapplication/ui/ListViewAdapter.kt index 2c15217..ef4a95e 100644 --- a/app/src/main/java/com/example/h_mal/myapplication/ListViewAdapter.kt +++ b/app/src/main/java/com/example/h_mal/myapplication/ui/ListViewAdapter.kt @@ -1,21 +1,20 @@ -package com.example.h_mal.myapplication +package com.example.h_mal.myapplication.ui import android.content.Context import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.net.Uri -import android.net.UrlQuerySanitizer -import android.opengl.Visibility import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter -import android.widget.Filter -import com.example.h_mal.myapplication.model.JsonObject +import com.example.h_mal.myapplication.R +import com.example.h_mal.myapplication.model.Repo import kotlinx.android.synthetic.main.repo_list_item.view.* //custom list adapter extends from array adater -class ListViewAdapter(context: Context, objects: MutableList) : - ArrayAdapter(context, 0, objects){ +class ListViewAdapter(context: Context, objects: MutableList) : + ArrayAdapter(context, 0, objects){ //populate each view override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { @@ -36,8 +35,15 @@ class ListViewAdapter(context: Context, objects: MutableList) : val dateString = item?.date?.split('T')?.get(0) view.date?.text = dateString + //control the language section of the view + populateLanguage(item, view) + + return view + } + + fun populateLanguage(item : Repo?, view : View){ //check if the language object is null - if (item.language != null){ + if (item?.language != null){ //language exists in object //view holdin the language to be visible view.lang_layout.visibility = View.VISIBLE @@ -49,25 +55,6 @@ class ListViewAdapter(context: Context, objects: MutableList) : //language was null therefore view to be hidden view.lang_layout.visibility = View.GONE } - - //apply on click listener to item - view.setOnClickListener{ - //click item opens then url - openLink(item.repoUrlString) - } - - return view - } - - //function for opening the link - fun openLink(urlString : String?){ - //open link to repo if the url is not nu;; - if (urlString != null){ - val openURL = Intent(Intent.ACTION_VIEW) - openURL.data = Uri.parse(urlString) - context.startActivity(openURL) - } - } //get the corresponding colour based on the programming language @@ -83,4 +70,19 @@ class ListViewAdapter(context: Context, objects: MutableList) : else -> null } } + + //function for opening the link + fun openLink(position: Int){ + val urlString = getItem(position)?.html_url + //open link to repo if the url is not null + if (urlString != null){ + val openURL = Intent(Intent.ACTION_VIEW) + openURL.addFlags(FLAG_ACTIVITY_NEW_TASK) + openURL.data = Uri.parse(urlString) + context.startActivity(openURL) + } + + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/myapplication/ui/MainActivity.kt b/app/src/main/java/com/example/h_mal/myapplication/ui/MainActivity.kt new file mode 100644 index 0000000..b62603f --- /dev/null +++ b/app/src/main/java/com/example/h_mal/myapplication/ui/MainActivity.kt @@ -0,0 +1,144 @@ +package com.example.h_mal.myapplication.ui + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.support.v4.widget.SwipeRefreshLayout +import android.view.Menu +import android.view.View +import android.widget.SearchView +import com.example.h_mal.myapplication.Api.GetData +import com.example.h_mal.myapplication.R +import com.example.h_mal.myapplication.model.Repo +import kotlinx.android.synthetic.main.activity_main.* +import io.reactivex.disposables.CompositeDisposable +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.Retrofit + +import io.reactivex.schedulers.Schedulers +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.DisposableObserver + + +class MainActivity : AppCompatActivity() { + lateinit var searchView: SearchView + lateinit var myCompositeDisposable: CompositeDisposable + + val urlString = "https://api.github.com/orgs/square/" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + spinner.visibility = View.VISIBLE + + myCompositeDisposable = CompositeDisposable() + + //begin populating list + loadData() + + //set a listener for the swipe to refresh + swipe_refresh.setOnRefreshListener(swipeRefreshListener) + } + + //implement search interface in the menu + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + //inflate custom menu as our menu + menuInflater.inflate(R.menu.menu, menu) + //extract searchview + val searchItem = menu?.findItem(R.id.search) + //set searchview globally + searchView = searchItem?.actionView as SearchView + + return true + } + + override fun onDestroy() { + super.onDestroy() + + myCompositeDisposable?.clear() + } + + val swipeRefreshListener = SwipeRefreshLayout.OnRefreshListener{ + //populate list when pulling to refresh +// callData() + loadData() + } + + fun loadData(){ + //clear list before populating + list_view.adapter = null + + val requestInterface = Retrofit.Builder() + .baseUrl(urlString) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build().create(GetData::class.java) + + myCompositeDisposable.add(requestInterface.getData() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribeWith(object : DisposableObserver>() { + override fun onNext(t: List) { + handleResponse(t) + } + + override fun onComplete() { + + } + + override fun onError(e: Throwable) { + handleError() + } + })) + } + + private fun handleResponse(objectList: List) { + + spinner.visibility = View.GONE + + //if swipe refresh is refreshing then stop + swipe_refresh.isRefreshing = false + + if (objectList.isNotEmpty()){ + //list is not empty + empty_view.visibility = View.GONE + //custom list view adapter created + val adapterLV = ListViewAdapter(baseContext, objectList.toMutableList()) + //apply adapter to listview + list_view.adapter = adapterLV + + list_view.setOnItemClickListener { parent, view, position, id -> + adapterLV.openLink(position) + } + + //search view has its query change listener applied + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{ + + override fun onQueryTextSubmit(query: String?): Boolean { + + return true + } + + //as the test is changed the list is filtered + override fun onQueryTextChange(newText: String?): Boolean { + //filter list function + adapterLV.filter.filter(newText) + + return true + } + }) + } + + } + + fun handleError(){ + //if swipe refresh is refreshing then stop + swipe_refresh.isRefreshing = false + //list is empty + empty_view.visibility = View.VISIBLE + //progress bar hidden + spinner.visibility = View.GONE + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e7668de..c6a1ef3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + tools:context=".ui.MainActivity"> - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/repo_list_item.xml b/app/src/main/res/layout/repo_list_item.xml index 5ef36ef..04f1bef 100644 --- a/app/src/main/res/layout/repo_list_item.xml +++ b/app/src/main/res/layout/repo_list_item.xml @@ -66,12 +66,13 @@ more text more of a description." /> -