Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moster_02 get data from web service #2

Merged
merged 7 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,24 @@ dependencies {
// KTX
implementation 'androidx.core:core-ktx:1.0.1'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"

// This container view models and data
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

//add the retrofit2 dependencies
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.6.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
// This container view models and data

implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2'

//Coroutines: Web service calls in Android have to be made asynchronously and they have to be done in background threads.
// Web service calls in Android are asynchronous that is you have to set up the call
// and then respond in some fashion when the response comes back.
// The web request and response must be handled in background threads.
// In the past, Android developers had a variety of ways to work with background threads,
// but there's a new way using coroutines a feature of the kotlin language.
}
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.anelcc.monster">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/anelcc/monster/Global.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package com.anelcc.monster

const val LOG_TAG = "monsterLogging"
const val LOG_TAG = "monsterLogging"
const val WEB_SERVICE_URL = "https://774906.youcanlearnit.net/"
39 changes: 8 additions & 31 deletions app/src/main/java/com/anelcc/monster/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
package com.anelcc.monster

import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import com.anelcc.monster.data.Monster
import com.anelcc.monster.utilities.FileHelper
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.anelcc.monster.data.MonsterRepository

class MainViewModel(app: Application) : AndroidViewModel(app) {

private val listType = Types.newParameterizedType(
List::class.java, Monster::class.java
)

init {
val resourcesText = FileHelper.getTextFromResources(app, R.raw.monster_data)
Log.i(LOG_TAG, "Resources: $resourcesText")

val assetsText = FileHelper.getTextFromAssets(app, "monster_data.json")
Log.i(LOG_TAG, "Assets: $assetsText")

parseText(assetsText)
}

fun parseText(text: String) {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val adapter: JsonAdapter<List<Monster>> = moshi.adapter(listType)
//This is parsing the data
val monsterData = adapter.fromJson(text)

for (monster in monsterData ?: emptyList()) {
Log.i(LOG_TAG,"parseText: ${monster.name} (\$${monster.price})")
}
}
/*
The ViewModel is simply passing that LiveData object back to the user interface.
And the fragment, that is the user interface,
is only responsible for managing the presentation.
*/
private val dataRepo = MonsterRepository(app)
val monsterData = dataRepo.monsterData
}
4 changes: 2 additions & 2 deletions app/src/main/java/com/anelcc/monster/data/Monster.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.anelcc.monster.data

import com.squareup.moshi.Json
import com.google.gson.annotations.SerializedName

data class Monster (
@Json(name = "monsterName")
@SerializedName("monsterName")
val name: String,
val imageFile: String,
val caption: String,
Expand Down
62 changes: 62 additions & 0 deletions app/src/main/java/com/anelcc/monster/data/MonsterRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.anelcc.monster.data

import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import com.anelcc.monster.LOG_TAG
import com.anelcc.monster.WEB_SERVICE_URL
import com.google.gson.GsonBuilder
import com.squareup.moshi.Types
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.moshi.MoshiConverterFactory

class MonsterRepository(val app: Application) {

//instance of the class MutableLiveData
val monsterData = MutableLiveData<List<Monster>>()
private val listType = Types.newParameterizedType(
List::class.java, Monster::class.java
)

init {
//Co-routine scope pass in a dispatcher.
// There are a number of different dispatchers available in the co-routines library,
// but for Android you can typically choose between two.
// Dispatchers.io means do this on the background thread,
// while dispatchers.main means do it in the foreground thread.
CoroutineScope(Dispatchers.IO).launch {
callWebService()
}
}

/**
* WorkerThreat annotation. Is an indicator that this function will be called in a background threat.
*/
@WorkerThread
suspend fun callWebService() {
if (networkAvailable()) {
val retrofit = Retrofit.Builder()
.baseUrl(WEB_SERVICE_URL)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.build()
val service = retrofit.create(MonsterService::class.java)
val serviceData = service.getMonsterData().body() ?: emptyList()
monsterData.postValue(serviceData)
}
}

@Suppress("DEPRECATION")
private fun networkAvailable(): Boolean {
val connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo?.isConnectedOrConnecting ?: false
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/anelcc/monster/data/MonsterService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.anelcc.monster.data

import retrofit2.Response
import retrofit2.http.GET

interface MonsterService {
//Add the keyword suspend.
// That means that this function is designed to be called from with a coroutine.
// Coroutines can run either on the UI thread or in a background thread,
// but for a function to be part of a coroutine call,
// it must have the suspend keyword at the beginning of the function declaration.
@GET("feed/monster_data.json")
suspend fun getMonsterData(): Response<List<Monster>>
}
18 changes: 15 additions & 3 deletions app/src/main/java/com/anelcc/monster/main/MainFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.anelcc.monster.LOG_TAG
import com.anelcc.monster.MainViewModel

import com.anelcc.monster.R
import com.anelcc.monster.data.Monster
import kotlinx.android.synthetic.main.fragment_main.*

class MainFragment : Fragment() {

Expand All @@ -25,9 +27,19 @@ class MainFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
/*That will notify the ViewModel when changes happen. And then I need an Observer object.
Within the Observer object, I'll receive the data as a list of Monster objects. So I'll go back to my ViewModel.*/
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

val monster = Monster("Bob", "myfile", "a caption", "a description", .19, 3)
Log.i("monsterLogging", monster.toString())
viewModel.monsterData.observe(this, Observer {
val monsterNames = StringBuilder()
/*I'll reference it as it. That is, this is the value that was passed in when the observer class received changes to the data*/
for (monster in it) {
monsterNames.append(monster.name).append("\n")
//Log.i(LOG_TAG, "parseText: ${monster.name} (\$${monster.price})")
}
monster_name.text = monsterNames
})

return inflater.inflate(R.layout.fragment_main, container, false)
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/layout/fragment_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/monster_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
Expand Down