Lambdas with Receivers

Lambdas with receivers in Kotlin.

fun main() {

    println(countTo100())
    println(countTo99().toString()) // Call toString() here, on the returned StringBuilder object

    val employee = KotlinEmployee2("Jim", "Johnson", 2011)
    val employees = listOf(KotlinEmployee2("John", "Smith", 2012), KotlinEmployee2("Jane", "Wilson", 2015), KotlinEmployee2("Mary", "Johnson", 2010), KotlinEmployee2("Mike", "Jones",2002))

    // Let's pretend we don't know about the find() function in Collections
    findByLastName(employees, "Wilson")
    findByLastName(employees, "Watson")
    findByFirstName(employees, "Jane")
    findByFirstName(employees, "Joan")

    with (employee.firstName) { // Regular usage of 'with' function
        println(capitalize())
    }

    myWith(employee.firstName) {// Demo: This is a high-order function. It passes a function into a function
        println(capitalize())
    // Every time this is called a lambda object is created. This could result in excessive memory consumption. To avoid this, make the myWith function inline

        println(employee.run { firstName }) // Another example of an extension lambda which works with all data types. Takes one lambda as argument and returns the result of executing the lambda. Sends in 'this' as an argument, but the 'this' can be excluded when calling e.g. 'name' instead of 'this.name'.

        DbConnection.getConnection().let { connection -> ..... } // Connection will no longer be available outside the block, limiting its scope.
// .let can also be used as an alternative to testing against null:
variable?.let { someFunction } // This block will be executed if 'variable' is not null. 'it' (variable) has now also been cast to not-null
// It is also particularly useful for chaining manipulations together:
        println((employee.let{it.firstName.capitalize()} // Capitalise first name
                         .let{it + " Snr"} // Concatenate some text
                         .let{it.length} // Get its length
                         .let{it + 31}) // And add 31. Returns 38

        println(employee.apply {}) // Another example of an extension lambda which works with all data types. Returns the object it's applied to rather than the result of the execution. Really useful for calling functions on a newly created object:
        val employee2 = KotlinEmployee2(name = "Jack").apply{ name = "James" } // Immediately call a function on a newly created object
        println(employee2.name)

    "Some String".apply somestring@{ // Add label so we can apply an operation to it
        "Another String".apply {
            println(toLowerCase()) // Operates on "Another String"
            // If we want to perform an operation on "Some String" use labels:
            println(this@somestring.toUpperCase())
        }
    }


}

// The long way
//fun countTo100(): String {
//    val numbers = StringBuilder()
//    for (i in 1..99) {
//        numbers.append(i)
//        numbers.append(",")
//    }
//    numbers.append(100)
//    return numbers.toString() // numbers variable is used a lot
//}

fun countTo100() = with(StringBuilder()) { // 'with' converts the instance you pass to it into a RECEIVER
        for (i in 1..99) {
            append(i) // We can exclude the instance reference
            append(",") // Could also type this.append but less concise
        }
        append(100)
        toString()
    }

Further explanation:

// EXTENSION LAMBDA - this is the equivalent of the built-in 'with' function:
fun myWith(name: String, block: String.() -> Unit) { // block (common convention name, can be anything) is the name of the function definition for the operation. We use this name in the body of the myWith function
    // String.() specifies the class we are extending (the Receiver object), so that we can use myWith on it. Unit is the return type of this particular function i.e. no return type
    // So block is now an extension function on a String object, and it can be applied to a String. We can now apply the passed-in function to the passed-in argument.
    name.block()
    // When we call the myWith function on fish.name (above), fish.name is the name argument and capitalize() is the block function
    // capitalize() returns a copy of the passed-in String, and does not change the original String. Wrapping it in a printLine shows us the result
}

// 'apply' does the same as 'with' but always returns the receiver object

fun countTo99() = StringBuilder().apply {
    for (i in 1..98) {
        append(i) // We can exclude the instance reference
        append(",") // Could also type this.append but less concise
    }
    append(99) // Don't call toString() here as the whole StringBuilder object is returned
} // You could add .toString() here as well

// The long way
fun findByLastName(employees: List<KotlinEmployee2>, lastName: String) {
    for (employee in employees) {
        if (employee.lastName == lastName) {
            println("We have an employee with last name $lastName")
            return
        }
    }
    println("Nobody here with the last name $lastName")
}

fun findByFirstName(employees: List<KotlinEmployee2>, firstName: String) {
    employees.forEach { // We can use a lambda as this is a functional interface
        if (it.firstName == firstName) {
            println("we have an employee with first name $firstName")
            return // Returns from both the lambda and the function i.e. a non-local return. This will only work when the function that is taking the lambda is inlined
            // If we want to make it a local return we can label the lambda: employees.forEach returnBlock@ {     followed by    return@returnBlock. In this case it would result in the "Nobody..." String being printed
            // This is useful when using nested 'with' or 'apply' statements (see "Some String" example in main function)
        }
    }
    println("Nobody here with first name $firstName")
}

data class KotlinEmployee2(val firstName: String, val lastName: String, val startYear: Int) { // Data class overrides our toString for a legible output

}

 

Lambdas

Lambdas in Java.

// Easier way to work with interfaces that have just one method that has to be implemented (functional interfaces). Also true if more than one method but other(s) have default implementation
// Often used in place of anonymous classes
public class Lambdas {

    public static void main(String[] args) {
        new Thread(new CodeToRun()).start(); // One method

        new Thread(new Runnable() { // Another method - anonymous class. Still a lot of code to run one line of output
            @Override
            public void run() {
                System.out.println("Also printing from the Runnable");
            }
        }).start();

        // Can be replaced with a lambda
        new Thread(() -> System.out.println("Still printing from the Runnable")).start(); // Passes the println() directly to the Thread constructor
        // 3 parts to a lambda - 1. argument list (empty in this case) 2. arrow token 3. body (put code to be executed here) - use {} for multiple lines (remember ; when multiple lines):

        new Thread(() -> {
            System.out.println("Line one");
            System.out.println("Line two");
        }).start();

        Employee john = new Employee("John Doe", 30);
        Employee tim = new Employee("Tim Buchalka", 21);
        Employee jack = new Employee("Jack Hill", 40);
        Employee snow = new Employee("Snow White", 22);

        List<Employee> employees = new ArrayList<>();
        employees.add(john);
        employees.add(tim);
        employees.add(jack);
        employees.add(snow);

//        Collections.sort(employees, new Comparator<Employee>() {
//            @Override
//            public int compare(Employee employee1, Employee employee2) {
//                return employee1.getName().compareTo(employee2.getName());
//            }
//        });

        Collections.sort(employees, (employee1, employee2) -> employee1.getName().compareTo(employee2.getName()));

        for (Employee employee : employees) {
            System.out.println(employee.getName());
        }

//        String sillyString = doStringStuff(new UpperConcat() {
//            @Override
//            public String upperAndConcat(String s1, String s2) {
//                return s1.toUpperCase() + s2.toUpperCase();
//            }
//        }, employees.get(0).getName(), employees.get(1).getName());
//        System.out.println(sillyString);

        String sillyString = doStringStuff((s1, s2) -> s1.toUpperCase() + s2.toUpperCase(), employees.get(0).getName(), employees.get(1).getName()); // No return keyword required
        System.out.println(sillyString);

    }
    //What if the interface returns a value?
    public final static String doStringStuff(UpperConcat uc, String s1, String s2) {
        return uc.upperAndConcat(s1, s2);
    }
}

class CodeToRun implements Runnable {
    @Override
    public void run() {
        System.out.println("Printing from the Runnable");
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

interface UpperConcat {
    public String upperAndConcat(String s1, String s2);
}

 

Kotlin object keyword – Singletons, companion objects and object expressions

Use of the object keyword in Kotlin.

fun main() {
    println(CompanyCommunications.getTagLine()) // Call the class name as there is only one instance
    println(CompanyCommunications.getCopyRightLine())

    SomeOtherClass().printPrivateVar() // Will allow us access to privateVar despite being private, thanks to Companion object
    SomeClass2.SomeCompanion.accessPrivateVar() // Using companion name, but again redundant

    val someClass = SomeClass4.justAssign("this is the string as is")
    val someClass2 = SomeClass4.upperOrLowerCase("this is the string as is", false)
    println(someClass.someString)
    println(someClass2.someString)
//    val someClass3 = SomeClass4("This will not work") // Constructor is private so cannot instantiate directly

    var thisIsMutable = 45

    wantsSomeInterface(object: SomeInterface { // Uses object to create an instance of the someInterface interface - NOT a Singleton. Is generated and then destroyed. Can use multiple interfaces, unlike Java
        override fun mustImplement(num: Int): String {
            return "This is from mustImplement: ${num * 100 + thisIsMutable}" // This expression can access variables from outside it (val or var, doesn't need to be final), unlike Java
        }
    })
}

// 'object' keyword's 3 uses: Singletons, companion objects, object expressions

// Singletons
// //In Java, you would create a private constructor that allows only one instance which other classes can access
// In Kotlin, there can only be one instance of an 'object' class, so we use that:
object CompanyCommunications { // No class keyword needed. Also no constructors as instance is created instantaneously
    val currentYear = Year.now().value
    fun getTagLine() = "Our company rocks!"
    fun getCopyRightLine() = "Copyright \u00A9 $currentYear Our Company. All rights reserved."
}

// Companion Objects
// No static keyword in Kotlin - instead we can use top-level functions and properties, and Object declarations (above)
// But what if you want an equivalent to Java static (see Java objects below), where you can access functions and properties without creating an instance of the class?
// We create companion objects inside the class which can be accessed without having to generate an instance of the class
class SomeClass {

    companion object {
        private var privateVar = 6 // Because is private no getter/setter generated

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeClass2 {

    companion object SomeCompanion { // You can also name your companion object, but little use
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeOtherClass() {
    fun printPrivateVar () {
        println(SomeClass.accessPrivateVar()) // Could use SomeClass.Companion.accessPrivateVar() but redundant
    }
}

class SomeClass3 {

    val someString: String

    constructor(str: String) { // First secondary constructor
        someString = str
    }

    constructor(str: String, lowerCase: Boolean) { // Second secondary constructor
        if (lowerCase) {
            someString = str.toLowerCase()
        } else {
            someString = str.toUpperCase()
        }
    }
// But instead of doing it this way we can create a companion class that returns an instance of the class (see below)
}

class SomeClass4 private constructor(val someString: String){ // We want to implement the Factory pattern here. This class should generate instances of the class - nobody should be able to do it directly as in SomeClass3, so primary is made private

    companion object{
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar in SomeClass4: $privateVar")
        }

        fun justAssign(str: String) = SomeClass4(str) // Can only call the private constructor from inside class therefore controlling how class is instantiated...
        fun upperOrLowerCase(str: String, lowerCase: Boolean): SomeClass4 { // so to get an instance of this class you have to go through either the justAssign or upperOrLowerCase function
            if (lowerCase) {
                return SomeClass4(str.toLowerCase())
            } else {
                return SomeClass4(str.toUpperCase())
            }
        }

    }

}

// Object expressions - equivalent to an anonymous object in Java
interface SomeInterface {
    fun mustImplement(num: Int): String
}

fun wantsSomeInterface(si: SomeInterface) {
    println("Printing from wantsSomeInterface - ${si.mustImplement(22)}")
}

Companion object equivalent in Java

public class SomeClass {

    private static int privateVar = 6;

    public static void main(String[] args) {
        new SomeOtherClass().someOtherMethod();
    }

    public static void accessPrivateVar() {
        System.out.println("I'm accessing privateVar: " + privateVar);
    }
}
public class SomeOtherClass {

    public void someOtherMethod() {
        SomeClass.accessPrivateVar();
    }
}

 

Kotlin Interfaces

Interfaces in Kotlin.

fun main() {
    println(InheritingClass("A name").number2)
}

// Very similar in implementation to Java

interface MyInterface { // No primary constructor () as you never make an instance. Open not required, as extendable by default
    val number: Int // Can include properties in interfaces. This variable is currently abstract
//    val number2: Int = 50 // Initialising properties not allowed in interfaces. Instead:
    val number2: Int
        get() = 50 // Declare getter to return fixed value. However, no 'field' available here.
    fun myFunction(str: String): String
}

interface MySubInterface: MyInterface {
    fun mySubfunction(num: Int): String
}

class anotherSomething(override val number: Int) : MySubInterface { // Implement abstract property
    override fun myFunction(str: String): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun mySubfunction(num: Int): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

// A class can extend from another class and implement and interface at the same time:
open abstract class AbstractClass(val name: String) {
    open fun printName() = println("The name is $name")
    abstract fun aNumber(): Double
}

class InheritingClass(name: String): AbstractClass(name), MyInterface { // both inherits and implements interface
    override val number: Int = 5 // Implement abstract property
    override fun myFunction(str: String): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun aNumber(): Double {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

 

@DAO (Data Access Object) – Room

An interface (API) which allows us to interact with our SQLite database tables. This example corresponds to the @Entity and Build A Database entries. Once these have been coded use LiveData to observe the data.

Create a custom class to define your table, using Entity to annotate it

@Dao // Required
public interface ContactDao {

    @Query("SELECT * FROM contacts ORDER BY age") // Database query
    List<ContactEntry> loadAllContacts(); // When loadAllContacts() is  called it will return a List<ContactEntry> containing the results of the query

    @Insert
    void insertContact(ContactEntry contactEntry); // Inserts a new ContactEntry into the table

    @Update(onConflict = OnConflictStrategy.REPLACE) // Set to replace (other options: Abort, Fail, Ignore, Rollback)
    void updateContact(ContactEntry contactEntry); // Update an existing Contact entry

    @Delete
    void deleteContact(ContactEntry contactEntry); // Delete an existing Contact entry

    @Query("SELECT * FROM contacts WHERE name = :name") // We can use the 'name' variable in the query by prefixing it with :
    ContactEntry loadEntryByName(String name); // Returns the ContactEntry which matches the name input by the user
}

To generate the table you will need to Build A Database. To define the table you will need to use @Entity.

ClickListener on list Adapter

Add a ClickListener to a ListAdapter, in this case a RecyclerView.Adapter.

Within list Adapter class (called ItemListAdapter in this example):
    private final ListItemClickListener mOnClickListener; // Declare field for OnClickListener
    public interface ListItemClickListener { // Create custom interface for your OnClickListener
        void onListItemClick (int item);
    }
    public ItemListAdapter(int numberOfItems, ListItemClickListener listItemClickListener) { // Add as parameter to Adapter constructor
        mNumberItems = numberOfItems;
        mOnClickListener = listItemClickListener;
    }
    class NumberViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { // In class that generates item View implement View.OnClickListener
        public NumberViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(this); // Set an
        }
        @Override // Override onClick
        public void onClick(View v) {
            mOnClickListener.onListItemClick(getAdapterPosition()); // Call custom interface instance
        }
    }

Within Activity.

public class MainActivity extends AppCompatActivity implements ItemListAdapter.ListItemClickListener { // Implement the custom interface
        mAdapter = new ItemListAdapter(NUM_LIST_ITEMS, this); // Second parameter produces custom OnClickListener in constructor
    @Override // Override the onListItemClick from the custom interface
    public void onListItemClick(int item) {
        if (mToast != null) {
            mToast.cancel();
        }
        Context context = this;
        CharSequence text = "Item number: " + item;
        int duration = Toast.LENGTH_SHORT;
        mToast = Toast.makeText(context, text, duration);
        mToast.show();
    }
}

ud851-Exercises-student\Lesson03-Green-Recycler-View\T03.07-Exercise-RecyclerViewClickHandling
ud851-Sunshine-student\ud851-Sunshine-student\S03.02-Exercise-RecyclerViewClickHandling