Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Edistynyt mobiiliohjelmointi, 2.3.2023
- Edellisen luennon FeedbackReadFragmentin pohjalta jatketaan, lisätään binding layerin avulla Buttonille siirtymä FeedbackSendFragmentiin:
- 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.
- binding.buttonSendFeedback.setOnClickListener {
- Log.d("TESTI", "BUTTON TOIMII!")
- val action = FeedbackReadFragmentDirections.actionFeedbackReadFragmentToFeedbackSendFragment()
- it.findNavController().navigate(action)
- }
- getFeedbacks()
- return root
- }
- // saman fragmentin xml-ulkoasussa:
- <Button
- android:id="@+id/button_send_feedback"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="SEND FEEDBACK" />
- // FeedbackSendFragmentin xml-ulkoasu, lisätään edittextit sopivilla id:illä, esim. jotain tyyliin:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_margin="10dp"
- tools:context=".FeedbackSendFragment">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="10dp"
- android:text="Send us feedback!"
- android:textColor="#8229AF"
- android:textSize="22sp"
- android:textStyle="bold" />
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Your name"
- android:textStyle="bold" />
- <EditText
- android:id="@+id/editText_feedback_name"
- android:layout_width="match_parent"
- android:ems="10"
- android:layout_height="48dp"
- android:hint="Write your name"
- android:inputType="textPersonName" />
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Your location"
- android:textStyle="bold" />
- <EditText
- android:id="@+id/editText_feedback_location"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:ems="10"
- android:hint="Where are you?"
- android:inputType="textPersonName" />
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Your feedback"
- android:textStyle="bold" />
- <EditText
- android:id="@+id/editText_feedback_value"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:ems="10"
- android:hint="Please provide your feedback here"
- android:inputType="textPersonName" />
- <Button
- android:id="@+id/button_send_feedback_api"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Send!" />
- </LinearLayout>
- // FeedbackSendFragment, napin toiminta ja edittextit
- class FeedbackSendFragment : Fragment() {
- // 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
- // kun nappia painetaan, lähetetään edittextien arvot apufunktiolle (joka käyttää myöhemmin Volleytä)
- binding.buttonSendFeedbackApi.setOnClickListener {
- val name = binding.editTextFeedbackName.text.toString()
- val location = binding.editTextFeedbackLocation.text.toString()
- val value = binding.editTextFeedbackValue.text.toString()
- sendFeedback(name, location, value)
- }
- return root
- }
- // apufunktio datan lähettämiselle Directusiin, ottaa vastaan parametrina uuden
- // feedbackin yksityiskohdat (name, location, value)
- fun sendFeedback(name : String, location : String, value : String) {
- Log.d("TESTI", "${name} - ${location} - ${value}")
- }
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
- // FeedbackSendFragment, Volley-koodin kanssa
- class FeedbackSendFragment : Fragment() {
- // 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
- // kun nappia painetaan, lähetetään edittextien arvot apufunktiolle (joka käyttää myöhemmin Volleytä)
- binding.buttonSendFeedbackApi.setOnClickListener {
- val name = binding.editTextFeedbackName.text.toString()
- val location = binding.editTextFeedbackLocation.text.toString()
- val value = binding.editTextFeedbackValue.text.toString()
- sendFeedback(name, location, value)
- }
- return root
- }
- // apufunktio datan lähettämiselle Directusiin, ottaa vastaan parametrina uuden
- // feedbackin yksityiskohdat (name, location, value)
- fun sendFeedback(name : String, location : String, value : String) {
- Log.d("TESTI", "${name} - ${location} - ${value}")
- val JSON_URL = "https://xxxxxxxxx.directus.app/items/feedback?access_token=${BuildConfig.DIRECTUS_ACCESS_TOKEN}"
- var gson = GsonBuilder().create();
- // Request a string response from the provided URL.
- val stringRequest: StringRequest = object : StringRequest(
- Request.Method.POST, JSON_URL,
- Response.Listener { response ->
- Log.d("TESTI", "Lähetys Directusiin ok!")
- // tyhjennetään editTextit
- binding.editTextFeedbackName.setText("")
- binding.editTextFeedbackLocation.setText("")
- binding.editTextFeedbackValue.setText("")
- // näytetään lyhyt viesti käyttäjälle ja suljetaan fragment -> palautuu takaisin edelliseen fragmentiin
- Toast.makeText(context, "Thank you for your feedback!", Toast.LENGTH_SHORT).show()
- activity?.onBackPressed()
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("TESTI", 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 lennosta uusi Feedback-olio EditTextien arvojen pohjalta
- var f : Feedback = Feedback()
- f.name = name
- f.location = location
- f.value = value
- // muunnetaan Feedback-olio JSONiksi GSONin avulla
- newData = gson.toJson(f)
- Log.d("TESTI", 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
- }
- }
- // Basic Authin käyttäminen Androidissa:
- /// ks. https://apingweb.com/#basic
- class BasicAuthFragment : Fragment() {
- // change this to match your fragment name
- private var _binding: FragmentBasicAuthBinding? = 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 = FragmentBasicAuthBinding.inflate(inflater, container, false)
- val root: View = binding.root
- // the binding -object allows you to access views in the layout, textviews etc.
- getUserData()
- return root
- }
- fun getUserData() {
- val JSON_URL = "https://apingweb.com/api/auth/users"
- // Request a string response from the provided URL.
- val stringRequest: StringRequest = object : StringRequest(
- Request.Method.GET, JSON_URL,
- Response.Listener { response ->
- Log.d("TESTI", response)
- },
- Response.ErrorListener {
- // typically this is a connection error
- Log.d("TESTI", 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"
- // basic authin käyttäjätunnus lisätään Authorization-kenttänä
- // headerseihin -> username : salasana, eli erotetaan toisistaan kaksoispisteellä
- val authorizationString = "Basic " + Base64.encodeToString(
- ("admin" + ":" + "12345").toByteArray(), Base64.DEFAULT
- )
- headers.put("Authorization", authorizationString);
- 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
- }
- }
- // Temporary access token Androidissa, Directus:
- class TempAccessFragment : Fragment() {
- // TÄMÄ ESIMERKKI ON TEHTY HYVIN PITKÄLLE "SIITÄ MISTÄ AITA ON MATALIN"
- // jos haluat optimoida, ks. alla olevat kommentit
- // koodin logiikka on nyt 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 vuoksi
- // Android-sovellus tekee paljon turhia kyselyjä Directusiin, koska kirjautuminen tehdään
- // aina virheen sattuessa uudelleen
- // tästä tulee ongelmia käyttäjämäärien kasvaessa esim. tuhansiin aktiivisiin käyttäjiin.
- // tällaisessa tilanteessa jokainen säästetty ja optimoitu kysely Directusin rajapintaan
- // säästää Androidin käyttämää aikaa, akku sekä myös Directusista tulevaa laskua. Mitä vähemmän
- // Android -sovellus "rassaa" Directusin rajapintaa, sen halvempi ja energiatehokkaampi
- // oma sovellus on
- // Parannusehdotus 1:
- // hyödynnetään refresh tokenia, joka tulee loginin yhteydessä. Access token on vain 15min voimassa,
- // mutta refresh token on 7 päivää voimassa. Refresh tokenin avulla Directus antaa uuden 15min
- // voimassa olevan access tokenin sekä uuden refresh tokenin. Muista myös tehdä logiikka koodiin
- // sellaista tilannetta varten, jossa käyttäjä ei ole käyttänyt sovellusta yli 7 vuorokauteen
- // jotta ohjelma osaa silloin kirjautua kokonaan uudestaan Directusiin
- // Parannusehdotus 2:
- // Directus antaa paluupostina tiedon millisekunteina siitä, milloin access token -vanhenee
- // (900000 ms => 900 sek => 15min)
- // Voit koodata Android -ohjelmasi siten, että uutta access tokenia ei pyydetä koodilla, mikäli
- // ei ole kulunut vielä 15 min aiemmasta kirjautumisesta. tällä tavalla Android -sovellus
- // ei turhaan ole yhteydessä Directusiin, koska äppi itse pitää kirjaa siitä milloin
- // pitää kirjautua uusiksi
- // Parannusehdotus 3:
- // kaikki kirjautumiseen liittyvä Volley-logiikka on hyvä keskittää yhteen paikkaan, joko
- // kustomiin Application -luokkaan tai tehdä oikeaoppisesti Volley-kutsuille oma Singleton-luokka
- // ks. Googlella kuinka tämä toteutetaan
- // 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 = "https://xxxxxxxx.directus.app/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 = "https://xxxxxxx.directus.app/items/feedback"
- // if using username + password in the service (e.g. Directus), use these
- val username = BuildConfig.DIRECTUS_USERNAME
- val password = BuildConfig.DIRECTUS_PASSWORD
- // 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 = ""
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- _binding = FragmentTempAccessBinding.inflate(inflater, container, false)
- val root: View = binding.root
- binding.buttonDataAction.setOnClickListener {
- dataAction()
- }
- // the binding -object allows you to access views in the layout, textviews etc.
- return root
- }
- // 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 OBJECTS
- // 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()
- Log.d("ADVTECH", accessToken)
- 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"
- // IBM Cloud expects the Content-Type to be the following:
- // headers["Content-Type"] = "application/x-www-form-urlencoded"
- // for Directus, the typical approach works:
- headers["Content-Type"] = "application/json; charset=utf-8"
- return headers
- }
- // uncomment these methods if using IBM Cloud
- /*
- override fun getBodyContentType(): String? {
- return "application/x-www-form-urlencoded; charset=UTF-8"
- }
- @Throws(AuthFailureError::class)
- override fun getParams(): Map<String, String>? {
- val params: MutableMap<String, String> = HashMap()
- params["grant_type"] = "urn:ibm:params:oauth:grant-type:apikey"
- params["apikey"] = apikey
- return params
- }
- */
- // 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 2: REFRESH - Not needed by IBM Cloudant by default
- // use this if you're using refresh logic
- var loginRefreshRequest: StringRequest = object : StringRequest(
- Request.Method.POST, LOGIN_URL,
- Response.Listener { response ->
- Log.d("ADVTECH", "REFRESH: " + response)
- var responseJSON: JSONObject = JSONObject(response)
- accessToken = responseJSON.get("access_token").toString()
- loggedIn = true
- needsRefresh = false
- dataAction()
- },
- 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"
- headers["Content-Type"] = "application/json; charset=utf-8"
- headers["Authorization"] = "Bearer " + refreshToken
- return headers
- }
- }
- */
- // REQUEST OBJECT 3 : ACTUAL DATA -> FEEDBACK
- var dataRequest: StringRequest = object : StringRequest(
- Request.Method.GET, JSON_URL,
- Response.Listener { response ->
- Log.d("ADVTECH", response)
- 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 onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
- }
Add Comment
Please, Sign In to add comment