Two years after using the Kotlin language, I have something to say

Keywords: Java Android UI

  • Why can Kotlin write Android programs?
    Because the code written in Java language needs to be compiled into a. class file to execute, and the code written in Kotlin will also be compiled into a. class file. For the Android operating system, it does not need to care or care about the language in which the program is developed, as long as the final execution is some legal. class files.
  • Why do we use Kotlin to develop Android programs?
    1. A meaningless semicolon at the end of each line of code.
    2. The switch statement only supports int conditions (String is also supported after java version 1.8), and break should be added at the end of case.
    3. It is not a comprehensive object-oriented language, but a semi object-oriented language.
    4. String embedded expressions are not supported, and splicing strings is complex.
  • Kotlin learning materials

Kotlin-in-chinese
Kotlin minimalist tutorial
Use of let, with, run, apply and also functions of Kotlin series
kotlin learning notes: the object keyword introduces the implementation of static variables and static methods in java, as well as the use of @ JvmField and @ JvmStatic
Kotlin Learning Series: variable parameters

Android studio configuring Gradle

  • Parent build.gradle:
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    //Create a variable to store the current Kotlin version, so that it can be used in several different places
    ext.kotlin_version = '1.2.50'
    //Create variables to store the version number of anko library. Anko is a powerful Kotlin library used to simplify some Android tasks
    ext.anko_version='0.10.7'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
        //Add Kotlin build plug-in
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • Sub build.gradle:
apply plugin: 'com.android.application'
//Kotlin plug-in
apply plugin: 'kotlin-android'
//Kotlin Android extensions plugin
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "cn.lq.com.kotlinstudy"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Add Kotlin standard library dependency
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    //Add anko library dependency. Anko common is a small part of anko library that we need, so that we won't add unused parts.
    implementation"org.jetbrains.anko:anko-common:$anko_version"
}
  • Quick configuration method: when creating a project, directly check Include Kotlin support to create a Kotlin project

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-u0pagwfb-1637636618825) (/ / upload images. Jianshu. IO / upload_images / 4834678-6163dc2b0fefd9dc. PNG? Imagemogr2 / Auto orient / strip|imageview2 / 2 / w / 1050 / format / webp)]

Kotlin Basics

1. Methods and variables

method

package com.lg.www.kotlinstudy
//Kotlin extremely weakens the static concept and uses it as little as possible
// The first way to define static methods is to define top-level methods (methods that do not belong to any class)
fun staticMethod1(){

}

class KotlinTest {

    //The method is defined with fun, and the return value is followed by ":". There is no return value and there is no need to write
    /*Four qualifiers:
    *   public: Default qualifier, which can be omitted
    *   private
    *   protected
    *   internal:Same as module qualifier; The same module can call methods modified by internal, but not across modules.
    */
    private fun helloworld(arg: String): String {
        println(arg)
        return arg
    }

    fun helloworld(): String{
        return "hellow word"
    }
    //The method body with only one line of code can be simplified by connecting the return values with an equal sign
    fun helloworld() = "hellow word"

    /*companion object: 
    We need a class with some static properties, constants or functions,
    So that we can use it without creating an object. We can use the company object.
    This object is shared by all objects of this class. The properties or methods in the object are like static properties or methods in Java,
    But it is not a static property or method. In fact, it just creates a Companion static object to call the properties and methods inside.
    */
    companion object {
        //Associated method, similar to static method
        fun companionMethod(){}
        //The second way to define static methods is to add static annotations
        @JvmStatic
        fun staticMethod2(){}
    }

}

variable

package com.lg.www.kotlinstudy

class KotlinTest {

    //Any type (similar to Object in java)
    fun test(arg: Any) {
        /*Data type:
        In Kotlin, everything is an object. There are no primitive primitive primitive types like those in Java.
        This is very helpful because we can deal with all problems in a consistent way
        Available types.*/
        val num: Int = 8 // Declare and assign immediately

        /*Variable:
        Variables can be simply declared as variable (var modified) and immutable (val modified).
        It can be simply understood that var is writable and can be assigned multiple times in its life cycle;
        val is read-only and can only be assigned once. It cannot be re assigned later. This is similar to Java
        The final is very similar. But immutability is an important in Kotlin (and many other modern languages)
        The concept of.
        An immutable object means that it cannot change its state after instantiation. If you need a
        A modified version of this object will create a new object. This makes programming more robust
        Robustness and predictability. In Java, most objects are mutable, which means that anyone can access it
        The code of an object can be modified to affect other parts of the whole program.
        Immutable objects can also be said to be thread safe because they cannot be changed and do not need to define access control
        Because all threads access the same object.
        Therefore, in Kotlin, whenever possible, the constant value of val is preferred in Kotlin. Because in fact, in the process
        Using immutable variables in most places can bring many benefits, such as predictable behavior and thread safety.
        */
        // If a variable has no initial value type, it cannot be omitted
        var varTest: String
        val valTest: Int
        valTest = 10// Assignment after declaration

        //Variables do not need to declare types. When the cursor stays on the variable, press Ctrl+Q to view the variable types
        var a = 1 // Automatically infer 'Int' type
        a = 2
        println(a)
        val b = "b"
        //B = "C" / / the compiler will report an error: Val cannot be reassigned
        println(b)

        //Low precision types cannot be automatically converted to high precision types, such as Int cannot be automatically converted to Long
        val x: Int = 10
        //val y:Long = x // Type mismatch
        val y: Long = x.toLong()//You need to explicitly call the corresponding type conversion function for conversion

        //The is operator detects whether an expression is an instance of a type
        if (arg is String) {
            // `result ` is automatically converted to ` String within the condition branch`
            // The detected branch can be directly used as this type without explicit conversion
            println(arg::class) //class kotlin.String
            val length = arg.length
            arg.length
            println(length)
        }

        /*Raw string:
        Separated by triple quotation marks ("" "), the original string can contain line breaks and any other characters,
        Will be output as is without escape*/
        val rawString = """\a/b\\c//d"""
        println(rawString)// \a/b\\c//d

        //String template expression: connect variables with dollar sign ($) to facilitate string splicing
        val s = "Hello"
        val templateString = "$s has ${s.length} characters"
        println(templateString) //Hello has 5 characters
    }

}

2. Class and inheritance

To create a Class, select Class in the kind column, and you can switch the type by pressing the up and down keys on the keyboard

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-mxamn4l7-1637636618828) (/ / upload images. Jianshu. IO / upload_images / 4834678-0cb0c4c7756ee350. PNG? Imagemogr2 / Auto orient / strip|imageview2 / 2 / w / 363 / format / webp)]

primary constructor

//General class
class Invoice {
}

//Class with primary constructor
class Person constructor(firstName: String) {
}
//If the main constructor has no annotation or visibility description, the constructor keyword can be omitted
class Person(firstName: String) {
}
//If the constructor has an annotation or visibility declaration, the constructor keyword is indispensable and visibility should precede it
class Customer public @inject constructor (name: String) {...}

//The primary constructor cannot contain any code. The initialization code can be placed in an initialization block prefixed with init
class Customer(name: String) {
    init {
        println("Customer initialized with value ${name}")
    }
}
//The parameters of the main constructor can be used in the initialization block or at the attribute initialization declaration of the class
class Customer(name: String) {
    val customerName = name
}
//Properties can be declared and initialized in the main constructor at the same time
//If the class has no class body, you can omit braces
class Customer(val customerName: String) 

//Add the data modifier in front of the class. Kotlin will automatically generate four methods for the class: copy, toString, hashCode and equals
data class Customer(val customerName: String) 
  • Get methods will be generated automatically for the attributes modified by val in Kotlin, and get and set methods will be generated automatically for the attributes modified by var. for example, the customerName attribute above has an automatically generated get attribute
class KtTest {
    var name = ""
        //Replace automatically generated set and get methods
        set(value) {
            field = value.toLowerCase()
        }
        get() {
            return field.toUpperCase()
        }
}

Second level constructor

//Class can also have a secondary constructor, which needs to be prefixed with constructor
//If the class has a primary constructor, each secondary constructor must directly or indirectly proxy the primary constructor through another secondary constructor. Use the this keyword to proxy another constructor in the same class
class Person (firstName: String) {
    constructor(firstName: String, secondName: String) : this(firstName){
          ...
    }
}

/*In the JVM virtual machine, if all parameters of the main constructor have default values,
The compiler generates an additional parameterless constructor that uses the default value directly.
This makes it easier for Kotlin to use a parameterless constructor to create a library of class instances*/
class Customer(val customerName: String = "")
//To create an instance, Kotlin does not have the new keyword
val c = Customer()
val p = Person("Xiao Ming")

inherit

  • All classes in Kotlin have a common parent class Any, which is the default parent of a class without a parent class declaration:

class Example / / implicitly inherited from Any

  • Any is not java.lang.Object; In fact, it has no members except equals(),hashCode(), and toString().
  • To inherit an explicit parent class, you need to add a colon and a parent class after the class header:
//The open annotation is the opposite of final in java: it allows other classes to inherit this class. By default, all classes in kotlin are final
//If the class has a primary constructor, the parent class must be initialized immediately with parameters in the primary constructor.
open class Base(p: Int)
class Derived(p: Int) : Base(p)
/*If the class does not have a primary constructor, the base class must be initialized with the super keyword in each constructor,
Or delegate another constructor to do this. Note the different second level constructors in this case
 You can call different construction methods of the base class:*/  
class MyView : View {
    constructor(ctx: Context) : super(ctx) {
    }
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
    }
}

Replication method

//In kotlin, we insist on doing explicit things. Unlike java, kotlin needs to clearly annotate all members that can be copied and rewrite them:
open class Base {
    open fun v() {}
    fun nv() {}
}
//For a parent class with a parameterless constructor, the parameterless constructor can be used for immediate initialization during inheritance
class Derived() : Base() {
    override fun v() {}
}

The override annotation is required for Derived.v(). If not, the compiler will prompt. If there is no open annotation, it is illegal to declare the same function in a subclass like Base.nv(), either add override or do not duplicate. Members of open type are not allowed in final classes (classes without open annotation).

  • Members marked override are open and can be replicated in subclasses. If you don't want to be rewritten, add final:
open class AnotherDerived() : Base() {
    final override fun v() {}
}

Replication properties

open class Foo {
  open val x: Int = 3
}
class Bar1 : Foo() {
  override val x: Int = 9
}
//Duplicate the attribute in the main constructor, using the override keyword as part of the attribute declaration
class Bar2(override val x: Int) : Foo() {
}

Duplicate name method rule during replication

open class A {
    open fun f () { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // The member variable of the interface is open by default
    fun b() { print("b") }
}

class C() : A() , B {
    // If the compiler does not know which F method to choose, it will ask to copy f()
    override fun f() {
        super<A>.f() // Call A.f()
        super<B>.f() // Call B.f()
    }
}

abstract class

  • A class or some members may be declared abstract. An abstract method does not implement a method in its class. Remember that we don't need to add an open annotation to an abstract class or function. It is carried by default.
  • We can use an abstract member to duplicate a non abstract method with an open annotation, as shown below.
open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

3. Interface

  • The methods in the Kotlin interface can be implemented by default with the method body
interface MyInterface {
    fun bar()
    fun foo() {
        //The function body is optional
                println("foo")
    }
}

//Implementation of interface
//A class or object can implement one or more interfaces, separated by
class Child : MyInterface {
        //Methods that do not have a default implementation must be overridden 
        override fun bar () {
        //Function body
    }
}
  • Properties in interface
//Unlike the fact that attributes in Java interfaces must be constants, attributes in Kotlin interfaces can be abstract
interface MyInterface {
        //Abstract properties
    val property: Int // abstract
    //Provides the properties of the accessor's implementation, similar to constants in Java
    val propertyWithImplementation: String
        get() = "foo"

    fun foo() { 
        print(property)
    } 
}
//Implementation interface
class Child : MyInterface { 
    override val property: Int = 29
}

4. Logic control

4.1 condition judgment

if...else...

fun test() {
        val a = 100
        val b = 87
        //if else returns the result of executing the last line of code as a return value
        val bigger = if (a > b) {
            a
        } else {
            b
        }
        //Only one line of code can remove the brackets from the method body
        val bigger1 = if (a>b) a else b
    }

when (replace switch in Java, which is much more powerful)

//when can pass any object
    fun test(arg: Any) {
      when (arg) {
            0 -> print("OK")
            in 1..8 -> print("1..8")
            "OK", "Success" -> print("OK")
            "Hello" -> {
                println("Hello")
                print("World!")
            }
            is Button -> print("is Button")
            else -> {
                print("End")
            }
        }
    }

4.2 cycle

for loop

   //for i loop in Java has been canceled in Kotlin
    fun loopTest() {
        //for in loop
        for (i in 0..100) { //0<=i<=100

        }
        for (i in 0 until 100) { //0<=i<100

        }
        val list = listOf("a", "b", "c")
        for (i in list) {
            print(i)//a b c
        }
         val array = arrayListOf("1", "2", "3")
        for (i in array.indices) {
            println(array[i])
        }
        //Interval writing
        for (i in IntRange(1, array.size - 1)) {
            println(array[i])
        }
        //it is a collection object
        list.forEach {
            print(it) //a b c
        }
        //Index is the collection index and s is the collection object
        list.forEachIndexed { index, s ->

        }
    }

while loops, do... while loops are exactly the same as those in Java, which are omitted here

5. Null pointer protection

  • Why do I need null pointer protection
    Null pointer exceptions are the first exceptions in our usual development. If an object can be null, we need to specify it explicitly, and then check whether it is null before using it. With null pointer protection, you can save a lot of time debugging null pointer exceptions and solve the bug caused by null.
 //main function
    fun main(args: Array<String>) {
        //getContentLength(null) / / error: null can not be a value of a non null type string
        getContentLength1(null)//No error reporting
    }

    fun getContentLength(content: String): Int {
        return content.length
    }
    fun printUpperCase(content: String?) {
        //?. Indicates that if it is not empty, the object method will be executed. The following is to convert the string to uppercase. If it is empty, it will not be executed and null will be returned
        println(content?.toUpperCase())
    }
    //Add after parameter? Indicates that null is allowed
    fun getContentLength1(content: String?): Int {
        //return content.length / / an error is reported. Only safe (?.) or non null asserted (!!!) calls are allowed on a nullable receiver of type string?
        //return content?.length / / use No, because the return value is Int and not empty. When null is returned, the type does not match
        //Solution 1
        return if (content == null) 0 else content.length
        //Solution 2,?: Indicates that the result of the previous expression takes precedence. When the result of the previous expression is empty, the following result 0 is taken
        return content?.length ?: 0
        //Solution 3 The non null check is forcibly removed. This can be done when you are sure that the parameter is non null
        return content!!.length
    }

6. lambda expression

7. Use Kotlin to develop Android programs

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:text="Button2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

</android.support.constraint.ConstraintLayout>
  • MainActivity.kt
package com.lg.www.kotlinstudy

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button

class MainActivity : AppCompatActivity() {

    var button: Button? = null
    //lateinit delay initialization keyword. Make sure that the object has been initialized before calling the object method and is not empty
    lateinit var button2: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button = findViewById(R.id.button)
        //?. Resolve null issue
        button?.text = "abc"
        button?.setOnClickListener {

        }
        //Let method, which can execute all operation statements related to this object in the let structure diagram / code block
        //For example, all code references related to button can be executed in the following structure. it is the reference of button object in the structure
        button?.let {
            it.text = "def"
            it.setOnClickListener {

            }
        }

        button2.text = "123"
    }

}
  • Use the Kotlin Android Extensions library to optimize the binding layout, which is recommended by Google
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
//The xml of the layout corresponding to the Activity is introduced
//After that, we can access these views after setContentView is called. The name of the attribute is the id of the corresponding view in the XML
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.text = "abc"
        button2.text = "123"
    }

}

8. Comparison of Java and Kotlin writing methods

nameJavaKotlin
objectMainActivity.thisthis@MainActivity
classMainActivity.classMainActivity::class.java
static const static final String text = "";const val text = "" (note that static variables should be defined above the class)
Comparison typeif ("" instanceof String) {}if ("" is String) {}
Compare stringsequals==(Kotlin optimizes the writing method of string comparison, and the equals method is still used for other types of object comparison)

epilogue

What was the opportunity for you to decide to use Kotlin?
If I remember correctly, I suddenly received a push from Bugly in March 2016, which introduced the Kotlin language. After reading this article, I found that Kotlin is very similar to Swift language. It happened that I was fascinated by Swift at that time. What's more surprising is that Kotlin language can be used to develop Android. Therefore, suddenly there was a feeling of blood boiling. Quickly spent a few days browsing the official documentation and began to use Kotlin in the code.

Do you use Kotlin mainly for production or just for daily testing?
I use Kotlin almost all in the production environment. The first application that adds Kotlin code is in a financial company. The final product is half Kotlin and half Java. The second project is a P2P application. In this project, I am the person in charge of the project. I strictly restrict the use of Kotlin in the project script, otherwise the compilation will fail. Therefore, this P2P application is my first all Kotlin language project. At present, this project is still running online. The last project also uses the whole Kotlin language. It is a social application, which is also running online. In fact, in addition to these, Kotlin language is also widely used in some of my open source projects. At present, there are still a large number of projects developed in Kotlin language in the code cloud private library, which have not been published.

Have you encountered any problems in using the Kotlin language?
If there is no problem at all, it is a lie! Because I was an early user of the Kotlin language, when I used the Kotlin language, the Kotlin plug-in of Android Studio was still immature and often collapsed. Once there is a crash, the code prompt is tragic. You have to knock it by hand and look at the problem after compiling. In addition, there was a problem at that time: when Kotlin was used with the real database, it always reported an error. As for other problems, I don't seem to have encountered them.

Some people say that Kotlin is a new language, and it is inevitable that there are some bugs? What do you think of this statement?
First of all, it is not denied that Kotlin language may have some unknown bugs. But I think people who say this are more insecure about themselves. He is afraid of new things. He is worried. Suddenly he has to learn a new language. What should he do? What if I can't learn in a short time? In fact, Kotlin's Bug is almost negligible. I think this is about the same as the probability that you will buy $5 million in * *. Why do you say that? Let me give you an example. You must know Linux, but do you know the version number of the first version of Linux? Is 0.01. The first version was developed by Linus alone. It only displays a line of Hello world on the screen. Linus thinks this version of Linux is far from the official version, so he named it 0.01. It is true. I don't know how many versions I have experienced. Finally, Linux version 1.0 was released in 1994. This is a historic moment! But what I want to say is, do you know what 1.0 means for a huge system? The answer in two words is: stable! It has experienced the efforts of countless people day and night, as well as countless internal and public tests, which means that almost 99% of the bugs have been eliminated, and you can use them with complete confidence. The same is true for Kotlin. The Kotlin language project began in 2010. Imagine a project of 6 or 7 years, and it is also a product developed by the world's most rigorous programmers. Why don't you believe it? Therefore, I think the so-called Bug theory is just synonymous with fear and fear!

Some people say that because Kotlin language access costs, the company will restrict programmers from using Kotlin. What do you think?
This makes sense! In China, technology is always slower than other countries. Therefore, you can see that most iOS programmers in China are still developing in OC language. In fact, the Swift language has been released for four years. One of the reasons may be that the company leaders restrict the use of the team, because the Swift language really changes a lot in syntax. After upgrading, you need to use Xcode to help you automatically convert, which is probably the biggest headache for most iOS programmers. There is nothing we can do about the company's restrictions. However, I do not think this restriction is entirely justified. As I said above, since Apple officials are confident enough to use Xcode to help you automatically convert, it proves that this method has been very easy to use. I know someone will retort: nonsense, I have some problems during several transformations! But I want to say that there must be a problem. Like the automatic conversion of Java to Kotlin code, there will be some problems, but as long as you manually correct these problems, it will be normal. It is often just a few lines of code. Therefore, I think the reason for the slow follow-up of this technology is not only the company's restrictions, but also the "timidity" of the Chinese people.

Has using the Kotlin language made any changes to your life?
In fact, life has not changed at present. But it helped me a lot in my work. At least 30% of my time was saved in daily development. Moreover, the probability of abnormality in the production environment is also reduced. If this is also a change, it has indeed made some changes in my life.

Do you have any special feelings about using Kotlin language?
If I have to say something special, there is something I am particularly proud of. I used Kotlin before Google officially announced that Kotlin would become the official language. At that time, I predicted that Kotlin might become the official programming language of Android. When the Google IO conference ended and I woke up, Kotlin suddenly became the official language of Android. I'm proud that I never won a prize in buying * * and successfully predicted a language for the first time.

Do you have anything to say about the new Kotlin?
First of all, you must be fearless. Learning a new language will make you temporarily uncomfortable. It's just like running. The beginning is always difficult, but it will become easier and easier in the future, and slowly began to become enjoyment. Therefore, if you encounter problems in the process, don't be afraid. There are countless Kotlin programmers around the world who will help you solve the problems. If you want to solve the problem in time, you can join my Kotlin technical exchange group: 329673958. Finally, I wish you an early settlement of Kotlin.

Article transferred from https://www.jianshu.com/p/1c83bd577cbb If there is infringement, please contact to delete.

Posted by javamint on Mon, 22 Nov 2021 20:21:19 -0800