Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Edistynyt mobiiliohjelmointi, 23.3.2023
- ---------------------------------------
- Asennetaan Directus omalle tietokoneelle, ks. ohjeet Moodlessa Harjoituksen 3 alla. Asennuksen jälkeen jatketaan normaalisti Harjoitus 3 - Directus, kunnes dataa saadaan nettiselaimeen näkyviin. Lopulta tällaisten osoitteiden tulisi toimia Directusin ollessa päällä:
- Kaikki Feedback-data:
- http://localhost:8055/items/feedback?access_token=OMA_ACCESS_TOKEN_TÄHÄN
- Vain yksi Feedback-data:
- http://localhost:8055/items/feedback/1?access_token=OMA_ACCESS_TOKEN_TÄHÄN
- Uuden Feedbackin lähettäminen Insomniasta,
- Osoite: http://localhost:8055/items/feedback?access_token=OMA_ACCESS_TOKEN_TÄHÄN
- Method: POST
- JSON Body:
- {
- "name": "Insomnia Test",
- "location": "Interwebs",
- "value": "Toimiiko tämä myös täältä käsin??"
- }
- json2kt.comia varten tarvitaan yksittäinen Feedback-data ilman data-wrapperia, eli:
- {"id":2,"name":"Somebody Else","location":"Someplace","value":"This is some random other feedback value."}
- Generoituna tulee seuraavanlainen luokka:
- import com.google.gson.annotations.SerializedName
- data class Feedback (
- @SerializedName("id" ) var id : Int? = null,
- @SerializedName("name" ) var name : String? = null,
- @SerializedName("location" ) var location : String? = null,
- @SerializedName("value" ) var value : String? = null
- )
- Lisää Feedback-luokka projektiisi esim. datatypes -> feedback -kansioon/packageen.
- Lisätään AndroidManifestin application-tagin sisälle:
- android:usesCleartextTraffic="true"
- Tämän jälkeen tyhjennä emulaattori (wipe data) jotta asetus tulee käyttöön.
- Haetaan ensin raakadata omasta rajapinnasta, FeedbackReadFragment, esim:
- class FeedbackReadFragment : Fragment() {
- // TODO: Rename and change types of parameters
- // change this to match your fragment name
- private var _binding: FragmentFeedbackReadBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- _binding = FragmentFeedbackReadBinding.inflate(inflater, container, false)
- val root: View = binding.root
- // the binding -object allows you to access views in the layout, textviews etc.
- getFeedbacks()
- return root
- }
- fun getFeedbacks() {
- // tähän tulee lopuksi Volley-koodi, jolla haetaan dataa Directusista
- // Directusin data on JSONia, ja muunnamme sen käyttökelpoiseen muotoon
- // GSONilla. Tätä varten tarvitaan dataluokka json2kt.comin kautta.
- // this is the url where we want to get our data
- // Note: if using a local server, use http://10.0.2.2 for localhost. this is a virtual address for Android emulators, since
- // localhost refers to the Android device instead of your computer
- val JSON_URL = "http://10.0.2.2:8055/items/feedback?access_token=OMA_ACCESS_TOKEN_TÄHÄN"
- // Request a string response from the provided URL.
- val stringRequest: StringRequest = object : StringRequest(
- Request.Method.GET, JSON_URL,
- Response.Listener { response ->
- Log.d("ADVTECH", response)
- // response from API, you can use this in TextView, for example
- // Check also out the example below
- // Note: if you send data to API instead, this might not be needed
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("ADVTECH", it.toString())
- })
- {
- @Throws(AuthFailureError::class)
- override fun getHeaders(): Map<String, String> {
- // we have to specify a proper header, otherwise Apigility will block our queries!
- // define we are after JSON data!
- val headers = HashMap<String, String>()
- headers["Accept"] = "application/json"
- headers["Content-Type"] = "application/json; charset=utf-8"
- return headers
- }
- }
- // Add the request to the RequestQueue. This has to be done in both getting and sending new data.
- val requestQueue = Volley.newRequestQueue(context)
- requestQueue.add(stringRequest)
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
- // muokataan getFeedbacks-metodia, muutetaan GSON:lla raakadata listaksi Feedbackeja
- fun getFeedbacks() {
- // tähän tulee lopuksi Volley-koodi, jolla haetaan dataa Directusista
- // Directusin data on JSONia, ja muunnamme sen käyttökelpoiseen muotoon
- // GSONilla. Tätä varten tarvitaan dataluokka json2kt.comin kautta.
- // this is the url where we want to get our data
- // Note: if using a local server, use http://10.0.2.2 for localhost. this is a virtual address for Android emulators, since
- // localhost refers to the Android device instead of your computer
- val JSON_URL = "http://10.0.2.2:8055/items/feedback?access_token=OMA_ACCESS_TOKEN_TÄHÄN"
- var feedbacks : List<Feedback> = emptyList();
- val gson = GsonBuilder().setPrettyPrinting().create()
- // Request a string response from the provided URL.
- val stringRequest: StringRequest = object : StringRequest(
- Request.Method.GET, JSON_URL,
- Response.Listener { response ->
- Log.d("ADVTECH", response)
- // joudumme kaivamaan data-kentän kautta varsinaisen feedback-datan
- val jObject = JSONObject(response)
- val jArray = jObject.getJSONArray("data")
- // muutetaan "datan" alta löytyvä JSON listaksi feedbackeja
- feedbacks = gson.fromJson(jArray.toString() , Array<Feedback>::class.java).toList()
- // luodaan adapteri ListViewille, voit korvata tämän RecyclerViewillä myös!
- // Huom: tämä ei toimi suoraan, johtuen ListViewin vaatimuksesta
- // käyttää vain Stringejä, ks. punainen kommentti alempaa
- val adapter = ArrayAdapter(activity as Context, R.layout.simple_list_item_1, feedbacks)
- // muista myös lisätä ListView fragmentin ulkoasuun (xml)
- // ListView löytyy Design-valikosta otsikon "Legacy" alta
- binding.listViewFeedbacks.adapter = adapter
- binding.listViewFeedbacks.setAdapter(adapter)
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("ADVTECH", it.toString())
- })
- {
- @Throws(AuthFailureError::class)
- override fun getHeaders(): Map<String, String> {
- // we have to specify a proper header, otherwise Apigility will block our queries!
- // define we are after JSON data!
- val headers = HashMap<String, String>()
- headers["Accept"] = "application/json"
- headers["Content-Type"] = "application/json; charset=utf-8"
- return headers
- }
- }
- // Add the request to the RequestQueue. This has to be done in both getting and sending new data.
- val requestQueue = Volley.newRequestQueue(context)
- requestQueue.add(stringRequest)
- }
- // tämä toimii nyt, mutta data tulee rumassa muodossa ListViewiin. ListView osaa käsitellä vain String-dataa, mutta oma datamme on
- // tyyppiä Feedback. Voimme yliajaa Feedback-luokan toString-metodin, ja vaikuttaa siihen mitä ListView tulostaa listaan:
- data class Feedback (
- @SerializedName("id" ) var id : Int? = null,
- @SerializedName("name" ) var name : String? = null,
- @SerializedName("location" ) var location : String? = null,
- @SerializedName("value" ) var value : String? = null
- )
- {
- override fun toString(): String {
- return name + ": " + value
- }
- }
- // UUDEN FEEDBACKIN LÄHETTÄMINEN, FeedbackSendFragment.kt
- Pohjakoodi:
- class FeedbackSendFragment : Fragment() {
- // TODO: Rename and change types of parameters
- // change this to match your fragment name
- private var _binding: FragmentFeedbackSendBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- _binding = FragmentFeedbackSendBinding.inflate(inflater, container, false)
- val root: View = binding.root
- binding.buttonSendFeedback.setOnClickListener {
- // haetaan edittextien arvot ja lähetetään sendFeedback-funktioon
- }
- // the binding -object allows you to access views in the layout, textviews etc.
- return root
- }
- fun sendFeedback(name: String, location: String, value: String) {
- // tähän koodi, joka käynnistetään napin kautta (Submit)
- // lähetetään POST-kysely Volleylla Directusiin
- // bodyna uusi data JSON-muodossa ilman id:tä (käytetään GSONia
- // muuntamaan data JSONIksi)
- // haetaan uuden feedbackin tiedot EditTexteistä (3 kpl)
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
- // Volley mukaan sekä siirretään EditTextien tiedot apufunktioon. Luodaan uusi JSON-body Feedback-luokan ja GSONin avulla:
- class FeedbackSendFragment : Fragment() {
- // TODO: Rename and change types of parameters
- // change this to match your fragment name
- private var _binding: FragmentFeedbackSendBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- _binding = FragmentFeedbackSendBinding.inflate(inflater, container, false)
- val root: View = binding.root
- binding.buttonSendFeedback.setOnClickListener {
- // haetaan edittextien arvot ja lähetetään sendFeedback-funktioon
- var name = binding.editTextFeedbackName.text.toString()
- var location = binding.editTextFeedbackLocation.text.toString()
- var value = binding.editTextFeedbackValue.text.toString()
- sendFeedback(name, location, value)
- }
- // the binding -object allows you to access views in the layout, textviews etc.
- return root
- }
- fun sendFeedback(name: String, location: String, value: String) {
- // tähän koodi, joka käynnistetään napin kautta (Submit)
- // lähetetään POST-kysely Volleylla Directusiin
- // bodyna uusi data JSON-muodossa ilman id:tä (käytetään GSONia
- // muuntamaan data JSONIksi)
- val JSON_URL = "http://10.0.2.2:8055/items/feedback?access_token=uxlR0aabxnfb2gTo4os3on6y5K72NckW"
- // Request a string response from the provided URL.
- val stringRequest: StringRequest = object : StringRequest(
- Request.Method.POST, JSON_URL,
- Response.Listener { response ->
- // response from API, you can use this in TextView, for example
- // Check also out the example below
- // "Handling the JSON in the Volley response" for this part
- // Note: if you send data to API instead, this might not be needed
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("ADVTECH", it.toString())
- })
- {
- @Throws(AuthFailureError::class)
- override fun getHeaders(): Map<String, String> {
- // we have to specify a proper header, otherwise Apigility will block our queries!
- // define we are after JSON data!
- val headers = HashMap<String, String>()
- headers["Accept"] = "application/json"
- headers["Content-Type"] = "application/json; charset=utf-8"
- return headers
- }
- // let's build the new data here
- @Throws(AuthFailureError::class)
- override fun getBody(): ByteArray {
- // this function is only needed when sending data
- var body = ByteArray(0)
- try { // check the example "Converting a Kotlin object to JSON"
- // on how to create this newData -variable
- var newData = ""
- // rakennetaan uuden Feedbacking olio EditTextien datan pohjalta
- var f : Feedback = Feedback()
- f.location = location
- f.name = name
- f.value = value
- // muutetaan Feedback-olio -> JSONiksi
- var gson = GsonBuilder().create();
- newData = gson.toJson(f)
- Log.d("ADVTECH", newData)
- // JSON to bytes
- body = newData.toByteArray(Charsets.UTF_8)
- } catch (e: UnsupportedEncodingException) {
- // problems with converting our data into UTF-8 bytes
- }
- return body
- }
- }
- // Add the request to the RequestQueue. This has to be done in both getting and sending new data.
- val requestQueue = Volley.newRequestQueue(context)
- requestQueue.add(stringRequest)
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
- // TEMP ACCESS TOKENIN KÄYTTÄMINEN ANDROIDISSA, TÄSÄS TAPAUKSESSA DIRECTUSIN RAJAPINTA
- class TempAccessFragment : Fragment() {
- // HUOM! Tämä esimerkki on tehty hyvin pitkälle tyyliin "siitä mistä aita on matalin".
- // Jos haluat optimoida tätä rakennetta, ks. alla olevat kommentit
- // tällä hetkellä koodin logiikka on tämä:
- // jos datan hakemisessa tulee Auth-error -> kirjaudutaan kokonaan uudestaan rajapintaan.
- // tämäkin ratkaisu toimii (varsinkin pienillä käyttäjämäärillä), mutta tämän johdosta
- // Android-sovellus tekee paljon turhia kyselyjä Directusiin, koska kirjautuminen tehdään
- // aina virheen sattuessa tai fragmentin latautuessa uudelleen
- // tämä voi muodostua ongelmaksi, mikäli sovelluksella on tuhansia aktiivisia käyttäjiä.
- // tällaisessa tilanteessa jokainen säästetty ja optimoitu kysely Directusin rajapintaan
- // säästää Androidin käyttämää suoritusaikaa, akkua sekä myös Directusista tulevaa käyttölaskua.
- // Mitä vähemmän Android-sovellus "rassaa" Directusin rajapintaa, sen halvempi ja energiatehokkaampi
- // oma Android-sovellus on.
- // Parannusehdotus 1:
- // hyödynnetään refresh tokenia access tokenin uusimisessa (kevyempi operaatio kuin uudestaan kirjautuminen)
- // refresh token tulee samassa datassa kuin access token myös. Access token on 15min voimassa, ja refresh
- // token on 7 päivää voimassa. Refresh tokenin avulla voi pyytää uuden access tokenin, mikäli refresh token
- // on vielä voimassa. Jos myös refresh token vanhenee -> kirjaudutaan uudestaan. (tämä logiikka pitää koodata itse)
- // Parannusehdotus 2:
- // Directusin kirjautumisdatassa tulee mukana myös tieto siitä, kuinka kauan access token on voimassa kirjautumishetkestä
- // alkaen, oletuksena 900000 millisekuntia -> 900 sekuntia -> 15min
- // Voidaan koodata Android-sovellus siten, että niin kauan kuin aikaa on jäljellä, Directusiin ei lähetetä
- // yhtään kirjautumispyyntöä. Tällä tavalla Android-sovellus ei turhaan ole yhteydessä Directusiin,
- // koska äppi pitää itse kirjaa siitä milloin pitää kirjautua uusiksi.
- // Parannusehdotus 3:
- // kaikki kirjautumiseen liittyvä Volley-logiikka on hyvä keskittää yhteen paikkaan, joko kustomoituun
- // Application -luokkaan, tai tehdä (suositellumpi tapa) Volley-kutsuille om a Singleton-luokka.
- // ks. Google ja Volleyn dokumentaatio esimerkistä miten tämä tehdään.
- // change this to match your fragment name
- private var _binding: FragmentTempAccessBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
- // VARIABLES USED BY THE SESSION MANAGEMENT
- val LOGIN_URL = "http://10.0.2.2:8055/auth/login"
- // these should be placed in the local properties file and used by BuildConfig
- // JSON_URL should be WITHOUT a trailing slash (/)!
- val JSON_URL = "http://10.0.2.2:8055"
- // if using username + password in the service (e.g. Directus), use these
- val username = "DIRECTUSKÄYTTÄJÄN EMAIL"
- val password = "DIRECTUSKÄYTTÄJÄN SALASANA"
- // request queues for requests
- var requestQueue: RequestQueue? = null
- var refreshRequestQueue: RequestQueue? = null
- // state booleans to determine our session state
- var loggedIn: Boolean = false
- var needsRefresh: Boolean = false
- // stored tokens. refresh is used when our session token has expired
- // access token in this case is the same as session token
- var refreshToken = ""
- var accessToken = ""
- // fragment entry point
- override fun onViewCreated(view: View, savedInstanceState: Bundle?)
- {
- super.onViewCreated(view, savedInstanceState);
- requestQueue = Volley.newRequestQueue(context)
- refreshRequestQueue = Volley.newRequestQueue(context)
- // start with login
- loginAction()
- }
- // button methods
- fun loginAction()
- {
- Log.d("ADVTECH", "login")
- Log.d("ADVTECH", JSON_URL + " login")
- requestQueue?.add(loginRequest)
- }
- fun refreshLogin() {
- if (needsRefresh) {
- loggedIn = false
- // use this if using refresh logic
- //refreshRequestQueue?.add(loginRefreshRequest)
- // if using refresh logic, comment this line out
- loginAction()
- }
- }
- fun dataAction() {
- if (loggedIn) {
- requestQueue?.add(dataRequest)
- }
- }
- // REQUEST OBJECT 1: LOGIN
- var loginRequest: StringRequest = object : StringRequest(
- Request.Method.POST, LOGIN_URL,
- Response.Listener { response ->
- var responseJSON: JSONObject = JSONObject(response)
- // save the refresh token too if using refresh logic
- // refreshToken = responseJSON.get("refresh_token").toString()
- // this part depends completely on the service that is used
- // Directus uses the data -> access_token -approach
- // IBM Cloud handles the version in comments
- // accessToken = responseJSON.get("access_token").toString()
- accessToken = responseJSON.getJSONObject("data").get("access_token").toString()
- loggedIn = true
- // after login's done, get data from API
- dataAction()
- Log.d("ADVTECH", response)
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("ADVTECH", it.toString())
- }) {
- @Throws(AuthFailureError::class)
- override fun getHeaders(): Map<String, String> {
- // we have to provide the basic header info
- // + Bearer info => accessToken
- val headers = HashMap<String, String>()
- headers["Accept"] = "application/json"
- // for Directus, the typical approach works:
- headers["Content-Type"] = "application/json; charset=utf-8"
- return headers
- }
- // use this to build the needed JSON-object
- // this approach is used by Directus, IBM Cloud uses the commented version instead
- @Throws(AuthFailureError::class)
- override fun getBody(): ByteArray {
- // this function is only needed when sending data
- var body = ByteArray(0)
- try {
- // on how to create this newData -variable
- var newData = ""
- // a very quick 'n dirty approach to creating the needed JSON body for login
- newData = "{\"email\":\"${username}\", \"password\": \"${password}\"}"
- // JSON to bytes
- body = newData.toByteArray(Charsets.UTF_8)
- } catch (e: UnsupportedEncodingException) {
- // problems with converting our data into UTF-8 bytes
- }
- return body
- }
- }
- // REQUEST OBJECT 3 : ACTUAL DATA -> FEEDBACK
- var dataRequest: StringRequest = object : StringRequest(
- Request.Method.GET, JSON_URL+"/items/feedback",
- Response.Listener { response ->
- Log.d("ADVTECH", response)
- // jos halutaan asettaa raakadata ulkoasuun TextViewiin
- //binding.textViewRawData.text = response
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("ADVTECH", it.toString())
- if (it is AuthFailureError) {
- Log.d("ADVTECH", "EXPIRED start")
- needsRefresh = true
- loggedIn = false
- refreshLogin()
- Log.d("ADVTECH", "EXPIRED end")
- }
- }) {
- @Throws(AuthFailureError::class)
- override fun getHeaders(): Map<String, String> {
- // we have to provide the basic header info
- // + Bearer info => accessToken
- val headers = HashMap<String, String>()
- // headers["Accept"] = "application/json"
- // headers["Content-Type"] = "application/json; charset=utf-8"
- headers["Authorization"] = "Bearer " + accessToken
- return headers
- }
- }
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- _binding = FragmentTempAccessBinding.inflate(inflater, container, false)
- val root: View = binding.root
- // the binding -object allows you to access views in the layout, textviews etc.
- // jos ulkoasussa on nappi jolla voi hakea dataa uudestaan ja testata
- // vieläkö access token on voimassa:
- /*
- binding.buttonGetTempData.setOnClickListener {
- dataAction()
- }
- */
- return root
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement