OOP in Kotlin and Android User Interface¶
Kotlin OOP¶
1 Classes¶
- Packages: are namespaces that contain classes and other packages
package com.example.myapp // declare the package that this class belongs to
class Foo {}
class Foo // no body
val foo = Foo() // create an instance of Foo, just like calling a function
class Foo (var count: Int) { // primary constructor, short-hand for declaring a property
fun increment() { count++ }
}
class Foo constructor(var count: Int) { // primary constructor, formal
fun increment() { count++ }
}
- If you have multiple multiple or private constructors, the
constructor
keyword in the class definition is required. - The
init
block is executed only onceafter
the primary constructor is called.
class Foo constructor(stringCount: String, val name: String) {
var count: Int = 0 // property
init {
println("Foo created with count $count") // only executed once, after primary constructor
// only properties defined above can be accessed, including those defined in the primary constructor
count = stringCount.toInt() // convert string to int
}
fun increment() { count++ }
/**
because `stringCount` does not have `val | var` keyword, it is not a property,
and cannot be accessed outside of the primary constructor or init block.
While `name` is a property, and can be accessed anywhere in the class.
*/
}
- Multiple Constructors can be defined using the
constructor
keyword, and must call the primary constructor. - The primary constructor can not execute any code other than initializing properties, but using the
init
block, you can execute code after the primary constructor is called (without the need of secondary constructors).
class Foo constructor(var count: Int, val con: String = 'Primary Constructor' ) { // primary constructor, with default value
init {
println("Foo created with count $count by constructor $con") // only executed once, after primary constructor
}
constructor(stringCount: String) : this(stringCount.toInt(), 'Constructor 1') { // call primary constructor
println("Foo created with count $count") // only executed once, after primary constructor
// execute more code if needed
}
constructor() : this(0, 'Constructor 2') // call primary constructor
}
val foo1 = Foo(1) // call primary constructor
val foo2 = Foo("2") // call secondary constructor
val foo3 = Foo() // call secondary constructor
/**
1. Foo created with count 1 by constructor Primary Constructor
2. Foo created with count 2 by constructor Constructor 1
2. Foo created with count 2
3. Foo created with count 0 by constructor Constructor 2
*/
- By default all classes extend
Any
, which is the equivalent ofObject
in Java. - Kotlin uses
:
to denote inheritance, andopen
keyword to denote a class that can be inherited from. - Classes Can not be inherited by default; a class must be declared
open
to be inherited from. - Any class methods Can NOT be overridden by default; a method must be declared
open
to be overridden. - The child class must use the
override
keyword to override an open method from the parent class. - An open function from a parent, is by default, open for all child classes to override, no matter how many levels of inheritance; unless a child class declares the function as
final
, then it is closed for child classes that inherit from it (upper levels can still override it).
class DefaultInherit: Any() { } // Foo extends Any, default no need to specify
open class Foo constructor (val status: Int) { // open keyword to denote a class that can be inherited from
open fun foo() { println("Foo") }
}
class Bar : Foo(1) { // Bar inherits from Foo, must call primary constructor passing in the required parameters
fun bar() { println("Bar") }
override fun foo() { println("Bar: overrides foo") } // override foo() method
}
val foo = Foo(1)
val bar = Bar()
foo.foo() // Foo
bar.foo() // Bar: overrides foo
bar.bar() // Bar
- Property Overriding:
- By default if the child has a property with the same name as the parent, will cause a compile error.
- In this case, the child class must use the
override
keyword to override the property from the parent class.
- You still have access to the parent properties or methods that overriden by the child class, by using the
super
keyword. - The
is
keyword is used to check if an object is an instance of a class or inherits from a class:if (obj is Foo) { ... }
- Using
is
withwhen
:
open class Animal {}
class Dog : Animal() {}
class Cat : Animal() {}
fun classifyAnimal(animal: Animal): Unit = when (animal) {
is Dog -> println("animal is a Dog")
is Cat -> println("animal is a Cat")
else -> println("animal is neither a Dog nor a Cat")
}
fun main() {
val dog = Dog()
val cat = Cat()
val animal: Animal =
Animal() // animal is a Dog, but declared as Animal; we can not tell what it is at compile time
classifyAnimal(dog) // animal is a Dog
classifyAnimal(cat) // animal is a Cat
classifyAnimal(animal) // animal is a Dog
}
- Casting can be done using the
as
keyword:val dog = animal as Dog
. - Kotlin has Smart Casting, which means that if you check if an object is an instance of a class, then you can use it as that class without casting it within the
if
block.
if (animal is Dog) {
animal.bark() // no need to cast animal to Dog, Compiler knows that animal is a Dog `Smart Casting`
// animal as Dog
}
// animal is an Animal, not a Dog
2 Properties¶
- All properties are instance properties by default, meaning that they are associated with an instance of the class.
- Properties can be initialized:
- When declared.
- In an
init
block. - Later in the class body, using a custom setter:
lateinit
keyword is used to denote that the property will be initialized later. The property must be initialized before it is used, otherwise it will throw an exception.lateinit
can only be used withvar
properties, and only with non-nullable types and not primitive types. by lazy
keyword is used to denote that the property will be initialized later. Compiler will initialize lazy property Only one time when it is first accessed.by lazy
is thread safe, and will only be initialized once, even if it is accessed by multiple threads. By lazy must provide lambda expression to initialize the property.- Fake properties: as functions may be called without parentheses, fake properties are used to distinguish between properties and functions.
- Constants: using the
const
keyword, the property must be a top-level or an object property, and must be of typeString
or a primitive type. Constants are compiled into static fields, and can be accessed from Java code.
class Foo (
val _whenDeclared: Int = 1, // initialized when declared
){
val whenDeclared: Int = this._whenDeclared // initialized when declared
val inInitBlock: Int // initialized later in init block
lateinit var later: String // initialized later in class body
init {
inInitBlock = 1 // initialized in init block
}
val lazy: String by lazy { // initialized later in class body
println("lazy initialized")
"lazy"
}
val fakeProperty: String get() = "fakeProperty" // fake property
companion object {
const val CONSTANT = "CONSTANT" // constant
}
fun foo() {
println("foo")
}
}
3 Visibility¶
Visibility: public
, private
, protected
, internal
¶
- Public: default visibility, visible everywhere.
- Private: visible only within the class (not subclasses), and within the file (if top-level or used outside of classes).
- Protected: visible within the class and subclasses.
- Internal: visible within the module (project).
Scope: Global
, Instance
, Local
¶
- Global:
- Top-level: declared outside of any class.
- Immutable globals have lifetime of the application.
- Mutable globals may get garbage collected.
- Instance: declared within a class, but outside of any function.
- Local: declared within a function.
4 Abstract Classes and Interfaces¶
Abstract Classes¶
- Use the
abstract
keyword to denote an abstract class. - Can have concrete methods or abstract methods/properties.
- Abstract classes are open by default, so they can be inherited from.
- Subclasses must implement all abstract methods/properties, or be abstract themselves and must be implemented by their subclasses.
Interfaces¶
- Use the
interface
keyword to denote an interface. - The rules of subclassing apply to interfaces (they are open by default, and must be implemented by their subclasses, can have both concrete or abstract methods).
- A class can implement multiple interfaces, but can only inherit from one class.
- Interfaces can NOT have concrete properties, only abstract properties.
- Interfaces can not manage state, to manage state, use an abstract class.
- Interfaces can not have constructors or protected members.
-
Interfaces can implement other interfaces, and provide implementations for their methods.¶
interface Aquatic {
fun swim() // abstract method
fun dive() { // concrete method
println("diving")
}
}
class Animal {
fun eat() {
println("eating")
}
abstract fun sleep() // abstract method
}
// interfaces do not have constructors, so they can not be instantiated and no need to use the `()` after the class name
abstract class Fish : Animal(), Aquatic { // abstract class that implements an interface, and inherits from a class
abstract override fun swim() // abstract method, left for subclasses to implement
override fun dive() { // concrete method, implemented.
println("diving")
}
}
class Shark : Fish() {
override fun swim() { // concrete method
println("swimming")
}
}
class Dolphin : Fish() {
override fun swim() { // concrete method
println("swimming")
}
}
5 Data Classes¶
- A
data class
in Kotlin (similar toAutoValue
in Java) defines an immutable class that has getters but no setters as well as methods:equals()
: compares the values of the properties.hashCode()
: generates a hash code based on the values of the properties.toString()
: generates a string representation of the object.copy()
: creates a copy of the object, and can be used to change some of the properties.
- Use the
data
keyword to denote a data class. - Data classes Can NOT be open or abstract, so they can not be inherited from (extended)
data class Person(
val name: String,
val age: Int,
val address: String
)
6 The object
Keyword¶
Singleton¶
- Using the
object
keyword, we can create a class and instantiate it at the same time. - Useful for creating singletons, or objects that are used only once.
- Objects can not have constructors, and can not be instantiated using the
()
after the class name. - Objects can inherit from classes and implement interfaces.
object Singleton {
fun foo() {
println("foo")
}
}
fun main() {
Singleton.foo() // no need to instantiate the object
}
Companion Objects¶
- Using the
companion object
keyword, we can create a class and instantiate it at the same time. - This is useful for creating static methods and properties within a class.
- The main advantage is that the companion object can access the private members of the class.
class Foo {
companion object {
var instanceCount = 0
accessPrivateMember(instance: Foo) {
println("accessing private member: ${instance.privateMember}")
}
}
companion object FooNamedCompanion { // named companion object
var myNamedCompanionObjectCount = 0
}
init {
Foo.instanceCount++
}
private val privateMember = "private member"
}
fun main() {
val foo1 = Foo()
val foo2 = Foo()
val foo3 = Foo()
println(Foo.instanceCount) // 3
Foo.accessPrivateMember(foo1) // accessing private member: private member
println(Foo.myNamedCompanionObjectCount) // 0, accessing the named companion object by the class name not the object name
}
Object Expressions¶
- Using the
object
keyword, we can create a class and instantiate it at the same time (thus creating an anonymous class).
abstract class OnClickListener {
abstract fun onClick(v: View)
}
class View {
fun setOnClickListener(listener: OnClickListener) {
// TODO something with this
}
}
fun clickyClickyClicky(view: View) {
view.setOnClickListener(object : OnClickListener() { // anonymous class, aka, object expression
override fun onClick(v: View) {
// TODO something useful
}
})
}
7 Nested Classes and Objects¶
- Classes can have nested classes and objects.
- Nested objects can be used to group constants and static methods (companion objects), or normal instance methods and properties (regular objects).
- Nested Classes are static by default, so they can not access the members of the outer class, unless you instantiate them within the class, and then they become nested objects.
- Inner classes are nested classes that are not static, so they can access the members of the outer class.
class Outer {
inner class Inner { // inner class, can access the members of the outer class
fun innerInner() { return this} // this refers to the inner class
fun outerInner() { return this@Outer} // this@Outer refers to the outer class
}
class StaticNested { // static nested class, can not access the members of the outer class
// this refers to the static nested class
// can not access the members of the outer class
}
class Nested {} // static
val nested = Nested() // nested class is now nested object, can access the members of the outer class
object NestedObject {} // instance
companion object NestedCompanionObject {} // static
}
fun main() {
val outer = Outer()
val myInner = outer.Inner()
val staticNested = Outer.StaticNested()
val nested = outer.nested
val nestedObject = Outer.NestedObject
val nestedCompanionObject = Outer.NestedCompanionObject
}
8 Enums and Sealed Classes¶
Enums¶
- Enums are used to define a set of constants.
- Enums are classes, so they can have properties and methods or passed as parameters to functions or other classes.
enum class Color {
RED, GREEN, BLUE
}
enum class HttpResponseCode(val code: Int, val description: String) {
OK(200, "OK"),
NOT_FOUND(404, "Not Found"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error")
toString() = "$code: $description"
}
fun main() {
val ok = HttpResponseCode.OK
println(ok.code) // 200
println(ok.description) // OK
println(ok) // 200: OK, implicitly calls toString()
Color.values().forEach { println(it) } // RED, GREEN, BLUE
val e404 = HttpResponseCode.valueOf("NOT_FOUND") // 404: Not Found, implicitly calls toString()
val e404 = HttpResponseCode.NOT_FOUND
e404.code // 404
e404.description // Not Found
e404.toString() // 404: Not Found
e404.ordinal // 1, the index of the enum. available on all enums
e404.name // NOT_FOUND, the name of the enum. available on all enums
}
Sealed Classes¶
- Sealed classes are used to define a set of classes that are related to each other.
- Used to control class hierarchies.
- Sealed Classes are abstract by default, so they can not be instantiated, but they can be extended.
sealed class PossibleShapes {
abstract fun info(): String
data class Circle(val radius: Double)
data class Rectangle(val width: Double, val height: Double)
data class Triangle(val base: Double, val height: Double)
}
class Shape : PossibleShapes() {
override fun info(): String {
return when (this) { // exhaustive when statement, no need for else, all cases are strictly coming from PossibleShapes
is Circle -> "Circle with radius $radius"
is Rectangle -> "Rectangle with width $width and height $height"
is Triangle -> "Triangle with base $base and height $height"
}
}
}
fun main() {
val shape = Shape()
val circle = shape.Circle(5.0)
val rectangle = shape.Rectangle(5.0, 10.0)
val triangle = shape.Triangle(5.0, 10.0)
circle.info() // Circle with radius 5.0
rectangle.info() // Rectangle with width 5.0 and height 10.0
triangle.info() // Triangle with base 5.0 and height 10.0
}
9 11 Kotlin for Android Tutorial¶
- Kotlin Android Development contains the following aspects:
- TextView: TextVew, AutoCompleteTextView (subclass of EditText), CheckedTextView.
- ScrollView: HorizontalScrollView, VerticalScrollView.
- ImageView.
- ListView.
- Button: ImageButton, RadioButton.
- EditText.
- Layouts: LinearLayout, RelativeLayout, TableLayout, FrameLayout, GridLayout, ConstraintLayout.
- Bar: SeekBar, RatingBar, ProgressBar.
- Switcher: TextSwitcher, ImageSwitcher, ViewSwitcher.
- TimeAndDatePicker: TimePicker, DatePicker.
- Spinner.
- TextClock.
- Chronometer.
- Notification.
- Toast.
- RadioGroup.
- Slider.
- WebView: simpl embedded browser within the app, see example
R
is a class that contains all the resources of the application, that are generated by the compiler during the build process. It is available in every activity and fragment.- All resources can be declared in XML files, and accessed in Kotlin code using the
R
class; ordynamically
created in Kotlin code (similar to document.createElement in JavaScript).
11 Android Development Tutorial¶
- Open
Project Structure
fromFile
menu, and set theProject SDK
to the latest version of Android SDK. - Every Module in the app has its own
build.gradle
file. - Static java dependencies (jar files), can be placed on
/app/libs
folder, and added to thebuild.gradle
files using thefile -> Project Structure -> Dependencies -> + -> File Dependency
menu.
10 12 More OOP¶
- In 10, Classes, Objects, Nested Classes, Inner Classes, and Type Aliases were covered.
- In 12, we have gone through different Object Oriented Concepts in Kotlin programming language.
- In 13, Basic OOP concepts were covered.
- In 14, A road map for all Kotlin provided by GeekForGeeks was covered. Refer to it when needed.
- In 15, A guide for building a simple Android app was covered.
15 Building a Simple Android App¶
- The
AndroidManifest.xml
is ready by Android Runtime while your app is executing. - Project Structure:
manifests
: contains theAndroidManifest.xml
file.java
:com.example.myfirstapp
: contains theMainActivity.kt
file.com.example.myfirstapp
(androidTest): all e2e tests.com.example.myfirstapp
(test): all unit tests.
res
:drawable
: contains all images.layout
: contains all layouts, which are XML files that define the UI.menu
: contains all menus.mipmap
: contains all icons, which are used in the launcher.navigation
: contains all navigation graphs, which are XML files that define the navigation between screens.values
: contains all strings, colors, and styles.strings.xml
: contains all strings.colors.xml
: contains all colors.- You can create your own XML files to define your own styles.
- Avd Manager is used to create and manage virtual devices.
References¶
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 8: Basic Classes. ↩
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 10: Properties. ↩
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 11: Visibility. ↩
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 12: Abstract Classes and Interfaces. ↩
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 13: Data Classes. ↩
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 14: The
object
Keyword. ↩ -
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 15: Nested Classes and Objects. ↩
-
Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 16: Enums and Sealed Classes. ↩
-
GeeksforGeeks. (2021, August 17). Kotlin android tutorial. Retrieved June 23, 2022, from https://www.geeksforgeeks.org/kotlin-android-tutorial/ ↩
-
Kotlin - class and objects. (n.d.). tutorialspoint. https://www.tutorialspoint.com/kotlin/kotlin_class_and_object.htm ↩↩
-
Kotlin android tutorial. (2021, January 21). TutorialKart. https://www.tutorialkart.com/kotlin-android-tutorial/ ↩↩
-
Kotlin object oriented programming concepts. (2021, January 21). TutorialKart. https://www.tutorialkart.com/kotlin/kotlin-oop/#gsc.tab=0 ↩↩
-
Kotlin OOP. (n.d.). W3Schools. https://www.w3schools.com/kotlin/kotlin_oop.php ↩
-
Kotlin programming language. (2021, November 24). GeeksforGeeks. Retrieved June 23, 2022, from https://www.geeksforgeeks.org/kotlin-programming-language/ ↩
-
Lmf. (2020, November 6). Build your first android app in kotlin. Android Developers. https://developer.android.com/codelabs/build-your-first-android-app-kotlin#0 ↩↩