tuomasvaltanen

Untitled

Mar 2nd, 2023 (edited)
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.34 KB | None | 0 0
  1. // Edistynyt mobiiliohjelmointi, 2.3.2023
  2.  
  3. Edellisen luennon FeedbackReadFragmentin pohjalta jatketaan, lisätään binding layerin avulla Buttonille siirtymä FeedbackSendFragmentiin:
  4.  
  5. override fun onCreateView(
  6. inflater: LayoutInflater,
  7. container: ViewGroup?,
  8. savedInstanceState: Bundle?
  9. ): View? {
  10. _binding = FragmentFeedbackReadBinding.inflate(inflater, container, false)
  11. val root: View = binding.root
  12.  
  13. // the binding -object allows you to access views in the layout, textviews etc.
  14. binding.buttonSendFeedback.setOnClickListener {
  15. Log.d("TESTI", "BUTTON TOIMII!")
  16.  
  17. val action = FeedbackReadFragmentDirections.actionFeedbackReadFragmentToFeedbackSendFragment()
  18. it.findNavController().navigate(action)
  19. }
  20.  
  21. getFeedbacks()
  22.  
  23. return root
  24. }
  25.  
  26.  
  27. // saman fragmentin xml-ulkoasussa:
  28.  
  29.  
  30. <Button
  31. android:id="@+id/button_send_feedback"
  32. android:layout_width="match_parent"
  33. android:layout_height="wrap_content"
  34. android:text="SEND FEEDBACK" />
  35.  
  36.  
  37. // FeedbackSendFragmentin xml-ulkoasu, lisätään edittextit sopivilla id:illä, esim. jotain tyyliin:
  38.  
  39. <?xml version="1.0" encoding="utf-8"?>
  40. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  41. xmlns:tools="http://schemas.android.com/tools"
  42. android:layout_width="match_parent"
  43. android:layout_height="match_parent"
  44. android:orientation="vertical"
  45. android:layout_margin="10dp"
  46. tools:context=".FeedbackSendFragment">
  47.  
  48. <TextView
  49. android:layout_width="match_parent"
  50. android:layout_height="wrap_content"
  51. android:layout_marginBottom="10dp"
  52. android:text="Send us feedback!"
  53. android:textColor="#8229AF"
  54. android:textSize="22sp"
  55. android:textStyle="bold" />
  56.  
  57. <TextView
  58. android:layout_width="match_parent"
  59. android:layout_height="wrap_content"
  60. android:text="Your name"
  61. android:textStyle="bold" />
  62.  
  63. <EditText
  64. android:id="@+id/editText_feedback_name"
  65. android:layout_width="match_parent"
  66.  
  67. android:ems="10"
  68. android:layout_height="48dp"
  69. android:hint="Write your name"
  70. android:inputType="textPersonName" />
  71.  
  72. <TextView
  73. android:layout_width="match_parent"
  74. android:layout_height="wrap_content"
  75. android:text="Your location"
  76. android:textStyle="bold" />
  77.  
  78. <EditText
  79. android:id="@+id/editText_feedback_location"
  80. android:layout_width="match_parent"
  81. android:layout_height="48dp"
  82. android:ems="10"
  83. android:hint="Where are you?"
  84. android:inputType="textPersonName" />
  85.  
  86. <TextView
  87. android:layout_width="match_parent"
  88. android:layout_height="wrap_content"
  89. android:text="Your feedback"
  90. android:textStyle="bold" />
  91.  
  92. <EditText
  93. android:id="@+id/editText_feedback_value"
  94. android:layout_width="match_parent"
  95. android:layout_height="48dp"
  96. android:ems="10"
  97. android:hint="Please provide your feedback here"
  98. android:inputType="textPersonName" />
  99.  
  100. <Button
  101. android:id="@+id/button_send_feedback_api"
  102. android:layout_width="match_parent"
  103. android:layout_height="wrap_content"
  104. android:text="Send!" />
  105. </LinearLayout>
  106.  
  107. // FeedbackSendFragment, napin toiminta ja edittextit
  108.  
  109. class FeedbackSendFragment : Fragment() {
  110. // change this to match your fragment name
  111. private var _binding: FragmentFeedbackSendBinding? = null
  112.  
  113. // This property is only valid between onCreateView and
  114. // onDestroyView.
  115. private val binding get() = _binding!!
  116.  
  117. override fun onCreateView(
  118. inflater: LayoutInflater,
  119. container: ViewGroup?,
  120. savedInstanceState: Bundle?
  121. ): View? {
  122. _binding = FragmentFeedbackSendBinding.inflate(inflater, container, false)
  123. val root: View = binding.root
  124.  
  125. // kun nappia painetaan, lähetetään edittextien arvot apufunktiolle (joka käyttää myöhemmin Volleytä)
  126. binding.buttonSendFeedbackApi.setOnClickListener {
  127. val name = binding.editTextFeedbackName.text.toString()
  128. val location = binding.editTextFeedbackLocation.text.toString()
  129. val value = binding.editTextFeedbackValue.text.toString()
  130.  
  131. sendFeedback(name, location, value)
  132. }
  133.  
  134. return root
  135. }
  136.  
  137. // apufunktio datan lähettämiselle Directusiin, ottaa vastaan parametrina uuden
  138. // feedbackin yksityiskohdat (name, location, value)
  139. fun sendFeedback(name : String, location : String, value : String) {
  140. Log.d("TESTI", "${name} - ${location} - ${value}")
  141. }
  142.  
  143. override fun onDestroyView() {
  144. super.onDestroyView()
  145. _binding = null
  146. }
  147. }
  148.  
  149. // FeedbackSendFragment, Volley-koodin kanssa
  150.  
  151.  
  152. class FeedbackSendFragment : Fragment() {
  153. // change this to match your fragment name
  154. private var _binding: FragmentFeedbackSendBinding? = null
  155.  
  156. // This property is only valid between onCreateView and
  157. // onDestroyView.
  158. private val binding get() = _binding!!
  159.  
  160. override fun onCreateView(
  161. inflater: LayoutInflater,
  162. container: ViewGroup?,
  163. savedInstanceState: Bundle?
  164. ): View? {
  165. _binding = FragmentFeedbackSendBinding.inflate(inflater, container, false)
  166. val root: View = binding.root
  167.  
  168. // kun nappia painetaan, lähetetään edittextien arvot apufunktiolle (joka käyttää myöhemmin Volleytä)
  169. binding.buttonSendFeedbackApi.setOnClickListener {
  170. val name = binding.editTextFeedbackName.text.toString()
  171. val location = binding.editTextFeedbackLocation.text.toString()
  172. val value = binding.editTextFeedbackValue.text.toString()
  173.  
  174. sendFeedback(name, location, value)
  175. }
  176.  
  177. return root
  178. }
  179.  
  180. // apufunktio datan lähettämiselle Directusiin, ottaa vastaan parametrina uuden
  181. // feedbackin yksityiskohdat (name, location, value)
  182. fun sendFeedback(name : String, location : String, value : String) {
  183. Log.d("TESTI", "${name} - ${location} - ${value}")
  184.  
  185. val JSON_URL = "https://xxxxxxxxx.directus.app/items/feedback?access_token=${BuildConfig.DIRECTUS_ACCESS_TOKEN}"
  186.  
  187. var gson = GsonBuilder().create();
  188.  
  189. // Request a string response from the provided URL.
  190. val stringRequest: StringRequest = object : StringRequest(
  191. Request.Method.POST, JSON_URL,
  192. Response.Listener { response ->
  193. Log.d("TESTI", "Lähetys Directusiin ok!")
  194.  
  195. // tyhjennetään editTextit
  196. binding.editTextFeedbackName.setText("")
  197. binding.editTextFeedbackLocation.setText("")
  198. binding.editTextFeedbackValue.setText("")
  199.  
  200. // näytetään lyhyt viesti käyttäjälle ja suljetaan fragment -> palautuu takaisin edelliseen fragmentiin
  201. Toast.makeText(context, "Thank you for your feedback!", Toast.LENGTH_SHORT).show()
  202. activity?.onBackPressed()
  203. },
  204. Response.ErrorListener {
  205. // typically this is a connection error
  206. Log.d("TESTI", it.toString())
  207. })
  208. {
  209. @Throws(AuthFailureError::class)
  210. override fun getHeaders(): Map<String, String> {
  211. // we have to specify a proper header, otherwise Apigility will block our queries!
  212. // define we are after JSON data!
  213. val headers = HashMap<String, String>()
  214. headers["Accept"] = "application/json"
  215. headers["Content-Type"] = "application/json; charset=utf-8"
  216. return headers
  217. }
  218.  
  219. // let's build the new data here
  220. @Throws(AuthFailureError::class)
  221. override fun getBody(): ByteArray {
  222. // this function is only needed when sending data
  223. var body = ByteArray(0)
  224. try { // check the example "Converting a Kotlin object to JSON"
  225. // on how to create this newData -variable
  226. var newData = ""
  227.  
  228. // rakennetaan lennosta uusi Feedback-olio EditTextien arvojen pohjalta
  229. var f : Feedback = Feedback()
  230. f.name = name
  231. f.location = location
  232. f.value = value
  233.  
  234. // muunnetaan Feedback-olio JSONiksi GSONin avulla
  235. newData = gson.toJson(f)
  236.  
  237. Log.d("TESTI", newData)
  238.  
  239. // JSON to bytes
  240. body = newData.toByteArray(Charsets.UTF_8)
  241. } catch (e: UnsupportedEncodingException) {
  242. // problems with converting our data into UTF-8 bytes
  243. }
  244. return body
  245. }
  246. }
  247.  
  248. // Add the request to the RequestQueue. This has to be done in both getting and sending new data.
  249. val requestQueue = Volley.newRequestQueue(context)
  250. requestQueue.add(stringRequest)
  251. }
  252.  
  253. override fun onDestroyView() {
  254. super.onDestroyView()
  255. _binding = null
  256. }
  257. }
  258.  
  259. // Basic Authin käyttäminen Androidissa:
  260. /// ks. https://apingweb.com/#basic
  261.  
  262. class BasicAuthFragment : Fragment() {
  263. // change this to match your fragment name
  264. private var _binding: FragmentBasicAuthBinding? = null
  265.  
  266. // This property is only valid between onCreateView and
  267. // onDestroyView.
  268. private val binding get() = _binding!!
  269.  
  270. override fun onCreateView(
  271. inflater: LayoutInflater,
  272. container: ViewGroup?,
  273. savedInstanceState: Bundle?
  274. ): View? {
  275. _binding = FragmentBasicAuthBinding.inflate(inflater, container, false)
  276. val root: View = binding.root
  277.  
  278. // the binding -object allows you to access views in the layout, textviews etc.
  279. getUserData()
  280.  
  281. return root
  282. }
  283.  
  284. fun getUserData() {
  285. val JSON_URL = "https://apingweb.com/api/auth/users"
  286.  
  287. // Request a string response from the provided URL.
  288. val stringRequest: StringRequest = object : StringRequest(
  289. Request.Method.GET, JSON_URL,
  290. Response.Listener { response ->
  291. Log.d("TESTI", response)
  292. },
  293. Response.ErrorListener {
  294. // typically this is a connection error
  295. Log.d("TESTI", it.toString())
  296. })
  297. {
  298. @Throws(AuthFailureError::class)
  299. override fun getHeaders(): Map<String, String> {
  300. // we have to specify a proper header, otherwise Apigility will block our queries!
  301. // define we are after JSON data!
  302. val headers = HashMap<String, String>()
  303. headers["Accept"] = "application/json"
  304. headers["Content-Type"] = "application/json; charset=utf-8"
  305.  
  306. // basic authin käyttäjätunnus lisätään Authorization-kenttänä
  307. // headerseihin -> username : salasana, eli erotetaan toisistaan kaksoispisteellä
  308. val authorizationString = "Basic " + Base64.encodeToString(
  309. ("admin" + ":" + "12345").toByteArray(), Base64.DEFAULT
  310. )
  311.  
  312. headers.put("Authorization", authorizationString);
  313.  
  314. return headers
  315. }
  316. }
  317.  
  318. // Add the request to the RequestQueue. This has to be done in both getting and sending new data.
  319. val requestQueue = Volley.newRequestQueue(context)
  320. requestQueue.add(stringRequest)
  321. }
  322.  
  323. override fun onDestroyView() {
  324. super.onDestroyView()
  325. _binding = null
  326. }
  327. }
  328.  
  329. // Temporary access token Androidissa, Directus:
  330.  
  331. class TempAccessFragment : Fragment() {
  332.  
  333. // TÄMÄ ESIMERKKI ON TEHTY HYVIN PITKÄLLE "SIITÄ MISTÄ AITA ON MATALIN"
  334. // jos haluat optimoida, ks. alla olevat kommentit
  335.  
  336. // koodin logiikka on nyt tämä:
  337.  
  338. // jos datan hakemisessa tulee Auth-error -> kirjaudutaan kokonaan uudestaan rajapintaan
  339. // tämäkin ratkaisu toimii (varsinkin pienillä käyttäjämäärillä), mutta tämän vuoksi
  340. // Android-sovellus tekee paljon turhia kyselyjä Directusiin, koska kirjautuminen tehdään
  341. // aina virheen sattuessa uudelleen
  342.  
  343. // tästä tulee ongelmia käyttäjämäärien kasvaessa esim. tuhansiin aktiivisiin käyttäjiin.
  344. // tällaisessa tilanteessa jokainen säästetty ja optimoitu kysely Directusin rajapintaan
  345. // säästää Androidin käyttämää aikaa, akku sekä myös Directusista tulevaa laskua. Mitä vähemmän
  346. // Android -sovellus "rassaa" Directusin rajapintaa, sen halvempi ja energiatehokkaampi
  347. // oma sovellus on
  348.  
  349. // Parannusehdotus 1:
  350. // hyödynnetään refresh tokenia, joka tulee loginin yhteydessä. Access token on vain 15min voimassa,
  351. // mutta refresh token on 7 päivää voimassa. Refresh tokenin avulla Directus antaa uuden 15min
  352. // voimassa olevan access tokenin sekä uuden refresh tokenin. Muista myös tehdä logiikka koodiin
  353. // sellaista tilannetta varten, jossa käyttäjä ei ole käyttänyt sovellusta yli 7 vuorokauteen
  354. // jotta ohjelma osaa silloin kirjautua kokonaan uudestaan Directusiin
  355.  
  356. // Parannusehdotus 2:
  357. // Directus antaa paluupostina tiedon millisekunteina siitä, milloin access token -vanhenee
  358. // (900000 ms => 900 sek => 15min)
  359. // Voit koodata Android -ohjelmasi siten, että uutta access tokenia ei pyydetä koodilla, mikäli
  360. // ei ole kulunut vielä 15 min aiemmasta kirjautumisesta. tällä tavalla Android -sovellus
  361. // ei turhaan ole yhteydessä Directusiin, koska äppi itse pitää kirjaa siitä milloin
  362. // pitää kirjautua uusiksi
  363.  
  364. // Parannusehdotus 3:
  365. // kaikki kirjautumiseen liittyvä Volley-logiikka on hyvä keskittää yhteen paikkaan, joko
  366. // kustomiin Application -luokkaan tai tehdä oikeaoppisesti Volley-kutsuille oma Singleton-luokka
  367. // ks. Googlella kuinka tämä toteutetaan
  368.  
  369. // change this to match your fragment name
  370. private var _binding: FragmentTempAccessBinding? = null
  371.  
  372. // This property is only valid between onCreateView and
  373. // onDestroyView.
  374. private val binding get() = _binding!!
  375.  
  376. // VARIABLES USED BY THE SESSION MANAGEMENT
  377. val LOGIN_URL = "https://xxxxxxxx.directus.app/auth/login"
  378.  
  379. // these should be placed in the local properties file and used by BuildConfig
  380. // JSON_URL should be WITHOUT a trailing slash (/)!
  381. val JSON_URL = "https://xxxxxxx.directus.app/items/feedback"
  382.  
  383. // if using username + password in the service (e.g. Directus), use these
  384. val username = BuildConfig.DIRECTUS_USERNAME
  385. val password = BuildConfig.DIRECTUS_PASSWORD
  386.  
  387. // request queues for requests
  388. var requestQueue: RequestQueue? = null
  389. var refreshRequestQueue: RequestQueue? = null
  390.  
  391. // state booleans to determine our session state
  392. var loggedIn: Boolean = false
  393. var needsRefresh: Boolean = false
  394.  
  395. // stored tokens. refresh is used when our session token has expired
  396. // access token in this case is the same as session token
  397. var refreshToken = ""
  398. var accessToken = ""
  399.  
  400. override fun onCreateView(
  401. inflater: LayoutInflater,
  402. container: ViewGroup?,
  403. savedInstanceState: Bundle?
  404. ): View? {
  405. _binding = FragmentTempAccessBinding.inflate(inflater, container, false)
  406. val root: View = binding.root
  407.  
  408.  
  409. binding.buttonDataAction.setOnClickListener {
  410. dataAction()
  411. }
  412.  
  413. // the binding -object allows you to access views in the layout, textviews etc.
  414.  
  415. return root
  416. }
  417.  
  418. // fragment entry point
  419. override fun onViewCreated(view: View, savedInstanceState: Bundle?)
  420. {
  421. super.onViewCreated(view, savedInstanceState);
  422.  
  423. requestQueue = Volley.newRequestQueue(context)
  424. refreshRequestQueue = Volley.newRequestQueue(context)
  425.  
  426. // start with login
  427. loginAction()
  428. }
  429.  
  430. // button methods
  431. fun loginAction()
  432. {
  433. Log.d("ADVTECH", "login")
  434. Log.d("ADVTECH", JSON_URL + " login")
  435. requestQueue?.add(loginRequest)
  436. }
  437.  
  438. fun refreshLogin() {
  439. if (needsRefresh) {
  440. loggedIn = false
  441. // use this if using refresh logic
  442. //refreshRequestQueue?.add(loginRefreshRequest)
  443.  
  444. // if using refresh logic, comment this line out
  445. loginAction()
  446. }
  447. }
  448.  
  449. fun dataAction() {
  450. if (loggedIn) {
  451. requestQueue?.add(dataRequest)
  452. }
  453. }
  454.  
  455. // REQUEST OBJECTS
  456.  
  457. // REQUEST OBJECT 1: LOGIN
  458. var loginRequest: StringRequest = object : StringRequest(
  459. Request.Method.POST, LOGIN_URL,
  460. Response.Listener { response ->
  461.  
  462. var responseJSON: JSONObject = JSONObject(response)
  463.  
  464. // save the refresh token too if using refresh logic
  465. // refreshToken = responseJSON.get("refresh_token").toString()
  466.  
  467. // this part depends completely on the service that is used
  468. // Directus uses the data -> access_token -approach
  469. // IBM Cloud handles the version in comments
  470. // accessToken = responseJSON.get("access_token").toString()
  471. accessToken = responseJSON.getJSONObject("data").get("access_token").toString()
  472.  
  473. Log.d("ADVTECH", accessToken)
  474.  
  475. loggedIn = true
  476.  
  477. // after login's done, get data from API
  478. dataAction()
  479.  
  480. Log.d("ADVTECH", response)
  481. },
  482. Response.ErrorListener {
  483. // typically this is a connection error
  484. Log.d("ADVTECH", it.toString())
  485. }) {
  486. @Throws(AuthFailureError::class)
  487. override fun getHeaders(): Map<String, String> {
  488. // we have to provide the basic header info
  489. // + Bearer info => accessToken
  490. val headers = HashMap<String, String>()
  491. headers["Accept"] = "application/json"
  492.  
  493. // IBM Cloud expects the Content-Type to be the following:
  494. // headers["Content-Type"] = "application/x-www-form-urlencoded"
  495.  
  496. // for Directus, the typical approach works:
  497. headers["Content-Type"] = "application/json; charset=utf-8"
  498.  
  499. return headers
  500. }
  501.  
  502. // uncomment these methods if using IBM Cloud
  503. /*
  504. override fun getBodyContentType(): String? {
  505. return "application/x-www-form-urlencoded; charset=UTF-8"
  506. }
  507.  
  508.  
  509. @Throws(AuthFailureError::class)
  510. override fun getParams(): Map<String, String>? {
  511. val params: MutableMap<String, String> = HashMap()
  512.  
  513.  
  514. params["grant_type"] = "urn:ibm:params:oauth:grant-type:apikey"
  515. params["apikey"] = apikey
  516. return params
  517. }
  518. */
  519.  
  520. // use this to build the needed JSON-object
  521. // this approach is used by Directus, IBM Cloud uses the commented version instead
  522. @Throws(AuthFailureError::class)
  523. override fun getBody(): ByteArray {
  524. // this function is only needed when sending data
  525. var body = ByteArray(0)
  526. try {
  527. // on how to create this newData -variable
  528. var newData = ""
  529.  
  530. // a very quick 'n dirty approach to creating the needed JSON body for login
  531. newData = "{\"email\":\"${username}\", \"password\": \"${password}\"}"
  532.  
  533. // JSON to bytes
  534. body = newData.toByteArray(Charsets.UTF_8)
  535. } catch (e: UnsupportedEncodingException) {
  536. // problems with converting our data into UTF-8 bytes
  537. }
  538. return body
  539. }
  540.  
  541. }
  542.  
  543. /*
  544. // REQUEST OBJECT 2: REFRESH - Not needed by IBM Cloudant by default
  545. // use this if you're using refresh logic
  546. var loginRefreshRequest: StringRequest = object : StringRequest(
  547. Request.Method.POST, LOGIN_URL,
  548. Response.Listener { response ->
  549.  
  550. Log.d("ADVTECH", "REFRESH: " + response)
  551.  
  552. var responseJSON: JSONObject = JSONObject(response)
  553. accessToken = responseJSON.get("access_token").toString()
  554.  
  555. loggedIn = true
  556. needsRefresh = false
  557.  
  558. dataAction()
  559. },
  560. Response.ErrorListener {
  561. // typically this is a connection error
  562. Log.d("ADVTECH", it.toString())
  563. }) {
  564. @Throws(AuthFailureError::class)
  565. override fun getHeaders(): Map<String, String> {
  566. // we have to provide the basic header info
  567. // + Bearer info => accessToken
  568. val headers = HashMap<String, String>()
  569. headers["Accept"] = "application/json"
  570. headers["Content-Type"] = "application/json; charset=utf-8"
  571. headers["Authorization"] = "Bearer " + refreshToken
  572. return headers
  573. }
  574.  
  575. }
  576. */
  577.  
  578. // REQUEST OBJECT 3 : ACTUAL DATA -> FEEDBACK
  579. var dataRequest: StringRequest = object : StringRequest(
  580. Request.Method.GET, JSON_URL,
  581. Response.Listener { response ->
  582. Log.d("ADVTECH", response)
  583.  
  584. binding.textViewRawData.text = response
  585. },
  586. Response.ErrorListener {
  587. // typically this is a connection error
  588. Log.d("ADVTECH", it.toString())
  589.  
  590. if (it is AuthFailureError) {
  591. Log.d("ADVTECH", "EXPIRED start")
  592.  
  593. needsRefresh = true
  594. loggedIn = false
  595. refreshLogin()
  596.  
  597. Log.d("ADVTECH", "EXPIRED end")
  598. }
  599. }) {
  600. @Throws(AuthFailureError::class)
  601. override fun getHeaders(): Map<String, String> {
  602. // we have to provide the basic header info
  603. // + Bearer info => accessToken
  604. val headers = HashMap<String, String>()
  605. // headers["Accept"] = "application/json"
  606. // headers["Content-Type"] = "application/json; charset=utf-8"
  607. headers["Authorization"] = "Bearer " + accessToken
  608. return headers
  609. }
  610.  
  611. }
  612.  
  613. override fun onDestroyView() {
  614. super.onDestroyView()
  615. _binding = null
  616. }
  617. }
Add Comment
Please, Sign In to add comment