dev-notes/Android/Activity.kt.md
2021-09-22 19:17:55 +02:00

12 KiB

Android App Activity.kt

Logging

Log.i("tag", "logValue")    //info log
Log.d("tag", "logValue")    //debug log
Log.w("tag", "logValue")    //warning log
Log.e("tag", "logValue")    //error log
Log.c("tag", "logValue")    //critical log

Activity Life Cycle

Life Cycle

package com.its.<appname>

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    //entry point of the activity
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.<activity_xml>)
    }

    override fun onStart() {
    }

    override fun onResume() {
    }

    override fun onPause() {
    }

    override fun onStop() {
    }

    override fun onRestart() {
    }

    override fun onDestroy() {
    }
}

Passing data between activities

In laughing activity:

private fun openActivity() {

        //target to open Intent(opener, opened)
        val intent = Intent(this, Activity::class.java)

        //pass data to launched activity
        intent.putExtra("identifier", value)  

        startActivity(intent)  //launch another activity
    }

In launched activity:

val identifier = intent.get<Type>Extra("identifier")

Hooks

Resources Hooks

R.<resourceType>.<resourceName>    //access to resource

ContextCompat.getColor(this, colorResource)    //extract color from resources
getString(stringResource)    //extract string from resources

XML hooks

Changes in xml made in kotlin are applied after the app is drawn thus overwriting instructions in xml.

In activity.xml:

<View
    android:id="@+id/<id>"/>

in Activity.kt:

var element = findViewById(R.id.<id>)    //old method

<id>.popery = value    //access and modify view contents

Activity Components

Snackbar

Component derived from material design. If using old API material design dependency must be set in gradle.

In build.gradle (Module:app):

dependencies {
    implementation 'com.google.android.material:material:<sem_ver>'
}

In Activity.kt:

import com.google.android.material.snackbar.Snackbar

Snackbar
    .make(activityID, message, Snackbar.TIME_ALIVE)  //create snackbar
    .setAction("Button Name", { action })  //add button to snackbar
    .show()

Opening External Content

Opening URLs

val url = "https://www.google.com"
val intent = Intent(Intent.ACTION_VIEW)

intent.setData(Uri.parse(url))
startActivity(intent)

Sharing Content

val intent = Intent(Intent.ACTION_SEND)

intent.setType("text/plain")  //specifying shared content type
intent.putExtra(Intent.EXTRA_MAIL, "mail@address")  //open mail client and pre-compile field if share w/ mail
intent.putExtra(Intent.EXTRA_SUBJECT, "subject")
intent.putExtra(Intent.EXTRA_TEXT, "text")  //necessary since type is text
startActivity(Intent.startChooser(intent, "chooser title"))  //let user choose the share app

App Google Maps

Documentation

val uri = Uri.parse("geo: <coordinates>")
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.setPackage("com.google.android.apps.maps")  //app to be opened
startActivity(intent)

Make a call (wait for user)

Preset phone number for the call, user needs to press call button to initiate dialing.

fun call() {
    val intent = Intent(Intent.ACTION_DIAL)
    intent.setData(Uri.parse("tel: <phone number>"))
    startActivity(intent)
}

Make a call (directly)

In AndroidManifest.xml:

<uses-permission android:name="android.permission.CALL_PHONE" />
//https://developer.android.com/training/permissions/requesting

//intercept OS response to permission popup
override fun onRequestPermissionResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

fun checkCallPermission() {
    //check if permission to make a call has been granted
    if (ContextCompact.checkSelfPermission(context!!, android.Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
        // if permission has not been granted request it (opens OS popup, no listener available)
        // request code needs to be specific for the permission
        ActivityCompat.requestPermissions(context!!, arrayOf(android.Manifest.permission.CALL_PHONE), requestCode)
    } else {
        call()  //if permission has been already given
    }
}

@SuppressLint("MissingPermission")  // suppress warning of unhandled permission (handled in checkCallPermission)
fun call() {
    val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel: <phone number>"))
    startActivity(intent)
}

Lists (with RecyclerView)

A LayoutManager is responsible for measuring and positioning item views within a RecyclerView as well as determining the policy for when to recycle item views that are no longer visible to the user. By changing the LayoutManager a RecyclerView can be used to implement a standard vertically scrolling list, a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock layout managers are provided for general use.

Adapters provide a binding from an app-specific data set to views that are displayed within a RecyclerView.

var array: ArrayList<T>? = null  //create ArrayList of elements to be displayed
var adapter: RecyclerViewItemAdapter? = null  //create adapter to draw the list in the Activity

array.add(item)  //add item to ArrayList

// create LayoutManager for the recyclerView
val layoutManager = <ViewGroup>LayoutManager(context, <ViewGroup>LayoutManager.VERTICAL, reverseLayout: Bool)
recycleView.LayoutManager = layoutManager  // assign LayoutManager for the recyclerView

// handle adapter var containing null value
if (array != null) {
    adapter = RecyclerViewItemAdapter(array!!)  // valorize adapter with a adapter object
    recyclerView.adapter = adapter  // assign adapter to the recyclerView
}

// add or remove item

// tell the adapter that something is changed
adapter?.notifyDataSetChanged()

WebView

WebView Docs

webView.webViewClient = object : WebViewClient() {

    // avoid opening browsed by default, open webview instead
    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        view?.loadUrl(url)  // handle every url
        return true
    }

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {

        // stay in WebView until website changes
        if (Uri.parse(url).host == "www.website.domain") {
            return false
        }

        Intent(Intent.ACTION_VIEW, Uri.parse(url).apply){
            startActivity(this)  // open browser/app when the website changes to external URL
        }
        return true
    }

    webView.settings.javaScriptEnabled = true  // enable javascript
    webView.addJavaScriptInterface(webAppInterface(this, "Android"))  // create bridge between js on website an app

    // if in webView use android back butto to go to previous webpage (if possible)
    override fun onKeyDown(keyCode: Int, event: KeyEvent?) :Boolean {
        if (keyCode == KeyEvent.KEYCODE_BACK && webVew,canGoBack()) {  // if previous url exists & back button pressed
            webView.goBack()  // go to previous URL
            return true
        }
    }
}

Web Requests (Using Volley)

Volley Docs

Import & Permissions

Import in build.gradle:

implementation 'com.android.volley:volley:1.1.1'

Permissions in AndroidManifest.xml:

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

Make the request

Subsequent requests should be delayed to avoid allowing the user to make too frequent requests.

private lateinit var queue: RequestQueue

override fun onCreate(savedInstanceState: Bundle?){
    queue = Volley.newRequestQueue(context)
}

// response is a String
private fun simpleRequest() {

    var url = "www.website.domain"

    var stringRequest = StringRequest(Request.Method.GET, url, Response.Listener<String> {
        Log.d()
    },
    Response.ErrorListener{
        Log.e()
    })

    stringRequest.TAG = "TAG"  // assign a tag to the request

    queue.add(stringRequest) // add request to the queue
}

// response is JSON Object
private fun getJsonObjectRequest(){
    // GET -> jsonRequest = null
    // POST -> jsonRequest = JsonObject
    var stringRequest = JsonObjectRequest(Request.Method.GET, url, jsonRequest, Response.Listener<String> {
        Log.d()
        // read end use JSON
    },
    Response.ErrorListener{
        Log.e()
    })

    queue.add(stringRequest) // add request to the queue
}

// response is array of JSON objects
private fun getJSONArrayRequest(){
    // GET -> jsonRequest = null
    // POST -> jsonRequest = JsonObject
    var stringRequest = JsonArrayRequest(Request.Method.GET, url, jsonRequest, Response.Listener<String> {
        Log.d()
        // read end use JSON
    },
    Response.ErrorListener{
        Log.e()
    })

    queue.add(stringRequest) // add request to the queue
}


override fun onStop() {
    super.onStop()
    queue?.cancelAll("TAG")  //  delete all request with a particular tag when the activity is closed (avoid crash)
}

Parse JSON Request

Response.Listener { response ->
    var value = response.getSting("key")
}

Data Persistance

Singleton

Object instantiated during app init and is destroyed only on app closing. It can be used for data persistance since is not affected by the destruction of an activity.

// Context: Interface to global information about an application environment.
class Singleton constructor(context: Context) {

    companion object {

        @Volatile
        private var INSTANCE: Singleton? = null

        // synchronized makes sure that all instances of the singleton are actually the only existing one
        fun getInstance(context: Context) = INSTANCE ?: synchronized(this) {
            INSTANCE ?: Singleton(context).also {
                INSTANCE = it
            }
        }
    }
}

SharedPreferences

SharedPreferences Docs

Get a handle to shared preferences:

  • getSharedPreferences() — Use this if you need multiple shared preference files identified by name, which you specify with the first parameter. You can call this from any Context in your app.
  • getPreferences() — Use this from an Activity if you need to use only one shared preference file for the activity. Because this retrieves a default shared preference file that belongs to the activity, you don't need to supply a name.
val sharedPref = activity?.getSharedPreferences( getString(R.string.preference_file_key), Context.MODE_PRIVATE )
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)

Write to shared preferences:

To write to a shared preferences file, create a SharedPreferences.Editor by calling edit() on your SharedPreferences.

Pass the keys and values to write with methods such as putInt() and putString(). Then call apply() or commit() to save the changes.

val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
    putInt(getString(R.string.key), value)
    commit()  // or apply()
}

apply() changes the in-memory SharedPreferences object immediately but writes the updates to disk asynchronously. Alternatively, use commit() to write the data to disk synchronously. But because commit() is synchronous, avoid calling it from your main thread because it could pause the UI rendering.

Read from shared preferences:

To retrieve values from a shared preferences file, call methods such as getInt() and getString(), providing the key for the wanted value, and optionally a default value to return if the key isn't present.

val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val defaultValue = resources.getInteger(R.integer.default_value_key)
val value = sharedPref.getInt(getString(R.string.key), defaultValue)