Kotlin learning notes is a must for beginners

}



@Api("repos")

interface Repos {



    @Get("{owner}/{repo}/forks")

    fun forks(owner: String, repo: String)



}

}

object RetroApi {

const val PATH_PATTERN = """(\{(\w+)\})"""



val okHttp = OkHttpClient()

val gson = Gson()



val enclosing = {

    cls: Class<*> ->

    var currentCls: Class<*>? = cls

    sequence {

        while(currentCls != null){

            // enclosingClass gets the next class

            // yield adds the object to the sequence sequence being built

            currentCls = currentCls?.also { yield(it) }?.enclosingClass

        }

    }

}



//Inline specialization

inline fun <reified T> create(): T {

    val functionMap = T::class.functions.map{ it.name to it }.toMap() //Turn Pair of [function name, function itself] into map

    val interfaces = enclosing(T::class.java).takeWhile { it.isInterface }.toList() //Get the list of all interfaces



    println("interfaces= $interfaces")// Output [githubapi $users, githubapi]



    //foldRight starts from the right side of the interfaces sequence

    val apiPath = interfaces.foldRight(StringBuilder()) {

        clazz, acc ->

        // Get the url parameter value of the Api annotation of each interface class. If the url parameter is empty, use the class name as the url value

        acc.append(clazz.getAnnotation(Api::class.java)?.url?.takeIf { it.isNotEmpty() } ?: clazz.name)

            .append("/")

    }.toString()



    println("apiPath= $apiPath") // https://api.github.com/users/



    //Dynamic agent

    return Proxy.newProxyInstance(RetroApi.javaClass.classLoader, arrayOf(T::class.java)) {

        proxy, method, args ->

        //The abstract function in all functions is the method of the interface

        functionMap[method.name]?.takeIf { it.isAbstract }?.let {

            function ->



            //Method parameters

            val parameterMap = function.valueParameters.map {

                //The parameter name and the parameter value are placed together

                it.name to args[it.index - 1] //valueParameters contains receiver, so index-1 is required to correspond to args

            }.toMap()



            println("parameterMap= $parameterMap") //{name=bennyhuo}



            //{name} gets the parameter of Get annotation. If the annotation parameter is not empty, the annotation parameter is used. If it is empty, the method name is used

            val endPoint = function.findAnnotation<Get>()!!.url.takeIf { it.isNotEmpty() } ?: function.name



            println("endPoint= $endPoint") //{name}/followers



            //Regular found all results in endPoint that match "{owner}/{repo}/forks" where {xxx}

            val compiledEndPoint = Regex(PATH_PATTERN).findAll(endPoint).map {

                matchResult ->

                println("matchResult.groups= ${matchResult.groups}") // [MatchGroup(value={name}, range=0..5), MatchGroup(value={name}, range=0..5), MatchGroup(value=name, range=1..4)]

                println("matchResult.groups1.range= ${matchResult.groups[1]?.range}") // 0..5

                println("matchResult.groups2.value= ${matchResult.groups[2]?.value}") // name

                matchResult.groups[1]!!.range to parameterMap[matchResult.groups[2]!!.value]

            }.fold(endPoint) {

                acc, pair ->

                //The initial value of acc is endPoint, that is, {name}/followers

                println("acc= ${acc}") //  {name}/followers

                println("pair= ${pair}") // (0..5, bennyhuo) pair is a range to name

                acc.replaceRange(pair.first, pair.second.toString()) // Replace the string {name} from 0 to 5 in {name}/followers with bennyhuo

            }



            println("compiledEndPoint= ${compiledEndPoint}") //bennyhuo/followers



            //Splicing api and parameters

            val url = apiPath + compiledEndPoint

            println("url ==== $url")

            println("*****************")



            okHttp.newCall(Request.Builder().url(url).get().build()).execute().body()?.charStream()?.use {

                gson.fromJson(JsonReader(it), method.genericReturnType)//Return the parsing result of json

            }



        }

    } as T

}

}

fun main() {

//interface com.bennyhuo.kotlin.annotations.eg.GitHubApi

//println("enclosingClass=${GitHubApi.Users::class.java.enclosingClass}")



val usersApi = RetroApi.create<GitHubApi.Users>()

val user = usersApi.get("bennyhuo")

val followers = usersApi.followers("bennyhuo").map { it.login }

println("user ====== $user")

println("followers ======== $followers")

}



This example is still a little complex and difficult to understand. Some methods have not been touched and do not know what they mean. Here, a lot of printing methods are added. Print out the results so that you can know what they represent.



Example: annotation plus reflection Model mapping



This example is implemented in the previous reflection section model On the basis of the mapping example, add annotations to deal with those field names that are not in the same style, such as those in two objects`avatar_url` and `avatarUrl`Mutual mapping of.



//Do not write. The default is RUNTIME

//@Retention(AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.VALUE_PARAMETER)

annotation class FieldName(val name: String)

@Target(AnnotationTarget.CLASS)

annotation class MappingStrategy(val klass: KClass)

interface NameStrategy {

fun mapTo(name: String): String

}

//Double hump

object UnderScoreToCamel : NameStrategy {

// html_url -> htmlUrl

override fun mapTo(name: String): String {

    //First convert to a character array, and then fold

    return name.toCharArray().fold(StringBuilder()) { acc, c ->

        when (acc.lastOrNull()) { //The last acc is not empty

            '_' -> acc[acc.lastIndex] = c.toUpperCase() //If the last character of the last result is an underscore, replace the underscore position with the uppercase letter of the current character

            else -> acc.append(c) // Otherwise, splice directly

        }

        //Return acc

        acc

    }.toString()

}

}

//Hump turn

object CamelToUnderScore : NameStrategy {

override fun mapTo(name: String): String {

    //First convert to a character array, and then fold

    return name.toCharArray().fold(StringBuilder()) { acc, c ->

        when {

            c.isUpperCase() -> acc.append('_').append(c.toLowerCase()) //If it is a capital letter, spell an underline directly and then spell lowercase

            else -> acc.append(c)

        }

        //Return acc

        acc

    }.toString()

}

}

//Use the defined policy annotation, hump to underline

@MappingStrategy(CamelToUnderScore::class)

data class UserVO(

val login: String,

//@FieldName("avatar_url ") / / this is a single field, and annotations can only be added one by one

val avatarUrl: String,

var htmlUrl: String

)

data class UserDTO(

var id: Int,

var login: String,

var avatar_url: String,

var url: String,

var html_url: String

)

fun main() {

val userDTO = UserDTO(

    0,

    "Bennyhuo",

    "https://avatars2.githubusercontent.com/u/30511713?v=4",

    "https://api.github.com/users/bennyhuo",

    "https://github.com/bennyhuo"

)



val userVO: UserVO = userDTO.mapAs()

println(userVO)



val userMap = mapOf(

    "id" to 0,

    "login" to "Bennyhuo",

    "avatar_url" to "https://api.github.com/users/bennyhuo",

    "html_url" to "https://github.com/bennyhuo",

    "url" to "https://api.github.com/users/bennyhuo"

)



val userVOFromMap: UserVO = userMap.mapAs()

println(userVOFromMap)

}

inline fun <reified From : Any, reified To : Any> From.mapAs(): To {

return From::class.memberProperties.map { it.name to it.get(this) }

    .toMap().mapAs()

}

inline fun Map<String, Any?>.mapAs(): To {

return To::class.primaryConstructor!!.let {

    it.parameters.map { parameter ->

        parameter to (this[parameter.name]

                // let(this::get) is equivalent to let{this[it]} userDTO["avatar_url"]

            ?: (parameter.annotations.filterIsInstance<FieldName>().firstOrNull()?.name?.let(this::get))

                // Get the UserVO class annotation MappingStrategy kclass CamelToUnderScore, which is an object calss, objectInstance gets the instance, then calls mapTo to turn avatarUrl into avatar_. URL, finally call userDTO["avatar_". url"]

            ?: To::class.findAnnotation<MappingStrategy>()?.klass?.objectInstance?.mapTo(parameter.name!!)?.let(this::get)

            ?: if (parameter.type.isMarkedNullable) null

            else throw IllegalArgumentException("${parameter.name} is required but missing."))

    }.toMap().let(it::callBy)

}

}



If you don't write it here`@Retention(AnnotationRetention.RUNTIME)`The default is the runtime type.  

The following two expressions are equivalent:



parameter.annotations.filterIsInstance()

parameter.findAnnotation()



The following two expressions are equivalent:



let(this::get)

let{

this[it]

}



mapAs()Several things are done in the method:



1.  Try directly from the current Map Get in To Object with the same name,

2.  Try from To Get the parameter name to be converted according to the annotation above the field of the object, and then get it according to the name Map Values in

3.  Try to get To Get the processing class from the class annotation of the object, call the processing class method, hump to underline, and then get it according to the name Map Values in

4.  None of the above moves have been obtained, if To If the field of the object can accept null values, it is assigned null, Otherwise, throw the exception



Hump and underline transitions are a little windy there..



Example: annotation processor version Model mapping



![Insert picture description here](https://img-blog.csdnimg.cn/20210114141602798.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70#pic_center)  

![Insert picture description here](https://img-blog.csdnimg.cn/20210114141617715.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70#pic_center)  

![Insert picture description here](https://img-blog.csdnimg.cn/20210114141630891.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70#pic_center)  

This example will use some famous code generation libraries:



*   generate Java code:[JavaPoet]( )

*   generate Kotlin code:[KotlinPoet]( )



Both of them square The company's open source library,[JakeWharton]( )The masterpiece of the great God is mainly used in this example KotlinPoet´╝îThere is also a library written by the lecturer of this learning course.



dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

implementation "com.squareup:kotlinpoet:1.4.3"

implementation "com.bennyhuo.aptutils:aptutils:1.7.1"

implementation project(":apt:annotations")

}



Note statement:



@Retention(AnnotationRetention.BINARY)

@Target(AnnotationTarget.CLASS)

annotation class ModelMap



There is no need to keep annotations at run time, and the compilation will generate code, so the`AnnotationRetention.BINARY`



Annotation generation code:



package com.bennyhuo.kotlin.annotations.apt.compiler

import com.bennyhuo.aptutils.AptContext

import com.bennyhuo.aptutils.logger.Logger

import com.bennyhuo.aptutils.types.ClassType

import com.bennyhuo.aptutils.types.asKotlinTypeName

import com.bennyhuo.aptutils.types.packageName

import com.bennyhuo.aptutils.types.simpleName

import com.bennyhuo.aptutils.utils.writeToFile

import com.bennyhuo.kotlin.annotations.apt.ModelMap

import com.squareup.kotlinpoet.*

import javax.annotation.processing.*

import javax.lang.model.SourceVersion

import javax.lang.model.element.ExecutableElement

import javax.lang.model.element.TypeElement

import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy

//The type of annotation must be specified

@SupportedAnnotationTypes("com.bennyhuo.kotlin.annotations.apt.ModelMap")

@SupportedSourceVersion(SourceVersion.RELEASE_8)

class ModelMapProcessor: AbstractProcessor() {

override fun init(processingEnv: ProcessingEnvironment) {

    super.init(processingEnv)

    AptContext.init(processingEnv)

}



//fun Sample.toMap() = mapOf("a" to a, "b" to b)

//fun Map<String, V>.toSample() = Sample(this["a"] as Int, this["b"] as String)

override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {

    roundEnv.getElementsAnnotatedWith(ModelMap::class.java)

        .forEach {

            element ->

            element.enclosedElements.filterIsInstance<ExecutableElement>()

                .firstOrNull { it.simpleName() == "<init>" }

                ?.let {

                    val typeElement = element as TypeElement

                    FileSpec.builder(typeElement.packageName(), "${typeElement.simpleName()}\$\$ModelMap") //$$escape

                        .addFunction(

                            FunSpec.builder("toMap")

                                .receiver(typeElement.asType().asKotlinTypeName())

                                .addStatement("return mapOf(${it.parameters.joinToString {""""${it.simpleName()}" to ${it.simpleName()}""" }})")//mapOf("a" to a, "b" to b)

                                .build()

                        )

                        .addFunction(

                            FunSpec.builder("to${typeElement.simpleName()}")

                                .addTypeVariable(TypeVariableName("V"))

                                .receiver(MAP.parameterizedBy(STRING, TypeVariableName("V")))

                                .addStatement(

                                    "return ${typeElement.simpleName()}(${it.parameters.joinToString{ """this["${it.simpleName()}"] as %T """ } })", //Sample (this ["a"] as int, this ["B"] as string)% t is the template string. Replace it with the following parameters

                                    *it.parameters.map { it.asType().asKotlinTypeName() }.toTypedArray()

                                )

                                .build()

                        )

                        .build().writeToFile()

                }

        }

    return true

}

}



![Insert picture description here](https://img-blog.csdnimg.cn/20210114142758262.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70)  

This is the module of the annotation processor, and then create a new module to use it:  

![Insert picture description here](https://img-blog.csdnimg.cn/20210114142847127.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x5YWJjMTIzNDU2,size_16,color_FFFFFF,t_70)  

gradle Must pass in`kapt`To rely on the annotation processor's Library:



plugins {

id 'java'

id 'org.jetbrains.kotlin.jvm'

id 'org.jetbrains.kotlin.kapt'

}

group 'com.bennyhuo.kotlin'

version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {

jcenter()

}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"



kapt project(":apt:compiler")

summary

In fact, it's easy to master it. There are two main points:

  1. Find a set of good video materials and follow Daniel's sorted knowledge framework for learning.
  2. Practice more( The advantage of video is that it has a strong sense of interaction and is easy to concentrate)

You don't need to be a genius or have a strong talent. As long as you do these two points, the probability of success in the short term is very high.

For many junior and intermediate Android engineers, if they want to improve their skills, they often grope and grow by themselves. The learning effect of fragmentation is inefficient, long and helpless. The screenshots of the following materials were sorted out by me for several months. They are full of sincerity: they are especially suitable for Android programmers with 3-5 years of development experience.

CodeChina open source project: Android learning PDF + Architecture Video + interview documents + source notes

rg.jetbrains.kotlin.jvm'

id 'org.jetbrains.kotlin.kapt'

}

group 'com.bennyhuo.kotlin'

version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {

jcenter()

}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"



kapt project(":apt:compiler")

summary

In fact, it's easy to master it. There are two main points:

  1. Find a set of good video materials and follow Daniel's sorted knowledge framework for learning.
  2. Practice more( The advantage of video is that it has a strong sense of interaction and is easy to concentrate)

You don't need to be a genius or have a strong talent. As long as you do these two points, the probability of success in the short term is very high.

For many junior and intermediate Android engineers, if they want to improve their skills, they often grope and grow by themselves. The learning effect of fragmentation is inefficient, long and helpless. The screenshots of the following materials were sorted out by me for several months. They are full of sincerity: they are especially suitable for Android programmers with 3-5 years of development experience.

CodeChina open source project: Android learning PDF + Architecture Video + interview documents + source notes

[external chain picture transferring... (img-DUN1WmIX-1630934319940)]

Tags: Java Android Design Pattern kotlin

Posted on Mon, 06 Sep 2021 21:54:25 -0400 by danelkayam