Interview Questions and Answers for Java / Kotlin / Android Software Engineer

Roman Kryvolapov
128 min readFeb 24, 2022

This article began in 2020 as an attempt to systematize knowledge, and since then has undergone numerous updates and revisions. Here I have collected many questions and answers from interviews for the position of Middle / Senior Android Software Engineer, as well as just valuable information and snippets of code that can be useful for Android developers. Some answers contain condensed and paraphrased information from websites; in these cases, I usually indicate the source. Many code examples are taken and refactored for the purpose of anonymization from various projects I’ve worked on. This, by the way, seems to me like an interesting vector for the development of this article — to compile a database with good pieces of code that can be used in other projects. I apologize in advance for any inaccuracies or outdated information, if you notice an error — please write to:
Telegram @RomanKryvolapov or
roman.kryvolapov@gmail.com or
https://www.linkedin.com/in/roman-kryvolapov/
The article will be supplemented and updated as the desire and free time allow.
If it so happens that this article has been valuable to you, I won’t object to a token of appreciation at revolut.me/romanzjqo because it, like everything I do besides work, unfortunately has no monetization.
Last update in March 2023: added a bit of code examples generated by ChatGTP and edited. ChatGTP is not yet suitable for generating responses, its responses need to be carefully read and moderated, which became especially clear after the phrase “The Common Reuse Principle is one of the five SOLID principles”, probably after such a phrase the world’s smartest AI somewhere in the USA gave Robert Martin a wake-up call from a nightmare. When GPT-4 becomes available, I’ll try again, hoping it will be more precise in phrasing.

ru lang version: https://roman-kryvolapov.medium.com/%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B-%D0%B8-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%8B-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%B2%D1%8C%D1%8E-android-java-developer-51a4a9df86df

➤ What are the modifiers in Java and Kotlin?

For Java methods and variables:

private: accessible only within the class
default (unspecified): accessible within the package
protected: accessible within the package or from subclassed classes
public: accessible everywhere
static: the method or variable is static
transient: the variable is not subject to serialization and should not participate in overriding equals() and hashCode()
final for a method: the method cannot be overridden
final for a variable: the variable is a constant
abstract: the method of an abstract class is abstract and does not have an implementation

For Java classes:

default (unspecified): accessible within the package
public: accessible everywhere
final: the class cannot be subclassed
abstract: the class is abstract and can contain abstract methods

For Java interfaces:

default: starting with Java 8, a normal method within an interface, designed to reduce code duplication; if every implementation of the interface has the same method, it can be overridden if necessary. If a class implements two interfaces with default methods of the same name, a conflict occurs and the method must be overridden.
static: starting with Java 8, a static method in an interface, called only by the interface name, not by an instance. Static methods are not inherited by the implementing class and cannot be overridden. Starting with Java 9, it can be private, so it can be called from another static method.

In Kotlin, there are also:

internal: accessible within a module
public (unspecified)
open for a class: the class can be subclassed; otherwise, it cannot be,
open for a method: the method can be overridden; otherwise, it cannot be
object: used for declaring singletons and for creating anonymous objects, which serve as a replacement for anonymous inner classes in Java, or for internal parameters

val obj = object {
var num: Int = 0
}
obj.num = 1

vararg: allows passing a variable number of arguments for a parameter
init: initialization block
lateinit: property with late initialization

lateinit var file: File
if (this::file.isInitialized) {
}
if (::file.isInitialized) {
}

typealias: provides alternative names for existing types
::: reference to a class, function, or property

// reference to a class
val c = MyClass::class
// reference to a property
var x = 1
fun main(args: Array<String>) {
println(::x.get()) // will print "1"
::x.set(2)
println(x) // will print "2"
}
// reference to a function
fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // will print [1, 3]
// reference to a constructor
class Foo
fun function(factory : () -> Foo) {
val x : Foo = factory()

value class: an inline class that doesn’t exist in the bytecode — its value is used instead, intended for wrapping data, does not lead to performance degradation
constructor: secondary constructor
inline: a function modifier; the usage of higher-order functions (a higher-order function is one that takes functions as parameters or returns a function as a result) can lead to performance issues. The inline modifier affects both the function and the lambda passed to it: both will be inlined at the call site. Inlining functions can increase the amount of code generated, but if done within reason (not inlining large functions), it can improve performance, especially when calling functions with parameters of different types within loops.
noinline: if you want only some lambdas passed to an inline function to be inlined, you need to mark the non-inlined function parameters with the noinline modifier
data class: a data class where the compiler automatically generates the following members of the class from the properties declared in the primary constructor: equals()/hashCode(), toString(), componentN(), copy()
inner class: in Kotlin, static inner classes have no modifier other than class, while regular inner classes, which require an instance of the outer class for access, are marked with the inner class modifier
sealed class: adding the sealed modifier to a superclass limits the possibility of creating subclasses. All direct subclasses must be nested within the superclass. A sealed class cannot have descendants declared outside of the class. By default, a sealed class is open, and the open modifier is not required. Sealed classes are somewhat similar to enums. In a sealed class, as many subclasses as needed can be created to cover every case. Additionally, each subclass can have multiple instances, each carrying its own state. Each subclass of a sealed class has its own constructor with its own unique properties.

sealed class StringSource {
data class Text(val text: String) : StringSource()
data class Res(val resId: Int) : StringSource()
}

val text = StringSource.Text("123")
val textRes = StringSource.Res(123)

Sealed classes are like enums on steroids.
A sealed class can have subclasses, but they must all be located within the same file as the sealed class. Classes extending the descendants of a sealed class can be placed anywhere.
Sealed classes are abstract by nature and can contain abstract components.
The constructor of a sealed class is always private and cannot be altered.
Sealed classes cannot be instantiated.
Descendants of a sealed class can be of any type: a data class, an object, a regular class, or even another sealed class.
Also, it is important to remember that:
All variables in interfaces are final by default.
When inheriting and overriding, one can expand the access level from default -> protected -> public.
When inheriting and overriding, it is possible to narrow the level of exceptions, for example, Exception -> RuntimeException -> ArithmeticException
https://metanit.com/java/tutorial/3.3.php

➤ Here are the data types in Java along with their sizes:

Integer Types:

byte: 8 bits, ranges from -128 to 127.
short: 16 bits or 2 bytes, ranges from -32,768 to 32,767.
int: 32 bits or 4 bytes, ranges from -2,147,483,648 to 2,147,483,647.
long: 64 bits or 8 bytes, ranges from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
Note: If the value assigned to a long exceeds the maximum value for an int, the letter ‘l’ or ‘L’ should be appended to the number.

Floating-Point Types:

float: 32 bits or 4 bytes, ranges from approximately 1.4E-45 to 3.4E38.
double: 64 bits or 8 bytes, ranges from approximately 4.9E-324 to 1.8E308.
Character Type:

char: 16 bits or 2 bytes, ranges from ‘\u0000’ or 0 to ‘\uffff’ or 65,535.
Boolean Type:

boolean: Technically, Java Virtual Machine does not have a boolean size, but it is generally represented as 8 bits or 1 byte when used in arrays or wrapped in a Boolean object for simplicity.

Also, remember that:

String is not a primitive data type.
In Java, primitive types cannot be null; reference types can be null.
In Kotlin, both primitive and reference types can be nullable if they are declared as such (e.g., var i: Int? = null). Kotlin doesn’t have primitive types in explicit form; the compiler decides whether to use a primitive type or a wrapper. However, there is a possibility for this determination to be made.

val string: String? = null
if (string == "123") {
} else {
// is not "123" or null
}

You can assign values to variables not only in decimal but also in binary (0b0), octal (010), and hexadecimal (0x0F) systems, and you can write the character code in char.

If the number is large, you can separate it with an underscore for readability (1_000_000_000).

When a value exceeds the allowable range, it wraps around from the minimum value, meaning if a byte exceeds its maximum, instead of +128, you would get -128.

In Java, == compares references or primitives since primitives are stored in the stack with their values, not as references. In Kotlin, == actually invokes equals(), while references can be compared using ===.

In Kotlin, a String can either contain escape characters, in which case it is enclosed with normal quotes “123” or it cannot contain escape characters, then it is enclosed with triple quotes “””123"””.
https://ru.wikibooks.org/wiki/Java/%D0%A2%D0%B8%D0%BF%D1%8B_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85

➤ What is the execution order of a Java class

Superclass static variable initialization
(when the class is first accessed)

Superclass static initializer
(when the class is first accessed)

Static variable initialization
(when the class is first accessed)

Static initializer
(when the class is first accessed)

Superclass constructor
(if there is no superclass, then the Object class constructor,
if the superclass constructor has parameters, they must be passed during the call to the invoked class’s constructor using
super(parameters)

Instance initializer

Instance variable initialization

Class constructor
https://youtu.be/xWSZZwDpcGU

➤ What Java Collections do you know?

Interfaces:

interface Collection: the base interface for collections and other collection interfaces.
interface List: an ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted and can access elements by their integer index (position). It allows duplicate elements and inherits from Collection.
interface Set: a collection that contains no duplicate elements. This interface models the mathematical set abstraction and is inherited from Collection.
interface Map: an object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value. It is not a true collection but part of the Collections Framework. Includes methods like put (instead of add), entrySet (to convert the key-value pairs into a Set), keySet (to convert keys into a Set), and values (to convert values into a Collection).
Do not confuse the Collection interface with the Collections framework. Map does not inherit from the Collection interface and is a standalone entity within the Collections Framework.
interface Queue: a collection designed for holding elements prior to processing. It typically orders elements in a FIFO (first-in-first-out) manner. Inherits from Collection.
interface Deque: a linear collection that supports element insertion and removal at both ends. It can be used both as a queue (FIFO) and as a stack (LIFO). Inherits from Queue.
class Stack: extends class Vector, which implements the List interface, and provides a simple last-in-first-out (LIFO) stack mechanism.
interface Iterator: an interface to iterate over the elements in a collection (except Map). It includes methods like hasNext, next, and remove, as well as forEachRemaining for advanced iteration capabilities. The syntax for Iterator is designed to…

list.iterator().forEachRemaining((value) -> System.out.println(value)))

Classes:

List: AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector
Set: AbstractSet, ConcurrentHashMap.KeySetView, ConcurrentSkipListSet, CopyOnWriteArraySet, EnumSet, HashSet, JobStateReasons, LinkedHashSet, TreeSet
Map: AbstractMap, Attributes, AuthProvider, ConcurrentHashMap, ConcurrentSkipListMap, EnumMap, HashMap, Hashtable, IdentityHashMap, LinkedHashMap, PrinterStateReasons, Properties, Provider, RenderingHints, SimpleBindings, TabularDataSupport, TreeMap, UIDefaults, WeakHashMap
Queue: AbstractQueue, ArrayBlockingQueue, ArrayDeque, ConcurrentLinkedDeque, ConcurrentLinkedQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedList, LinkedTransferQueue, PriorityBlockingQueue, PriorityQueue, SynchronousQueue
Deque: ArrayDeque, ConcurrentLinkedDeque, LinkedBlockingDeque, LinkedList
In Kotlin, collections are divided into mutable and immutable.

ArrayList: an array-based list.
LinkedList: a doubly linked list.
Vector: similar to ArrayList, but all methods are synchronized.
PriorityQueue: an array-based queue sorted by a comparator; for complex classes, a comparator can be passed to the constructor.
SortedSet: a set with sorted elements.
NavigableSet: allows for retrieval of elements based on their values.
TreeSet: stores objects in an ascending sorted order, operates based on TreeMap.
HashSet: similar to HashMap but the key is the object itself, operates based on HashMap.
LinkedHashSet: maintains the insertion order of objects, operates based on LinkedHashMap.
HashMap: a key-value array; before a collision, it is an array, after a collision, it becomes a singly linked list, which, upon reaching a certain threshold, transforms the linked list node (storing the next element) into a red-black tree TreeNode.
Hashtable: a synchronized version of HashMap.
LinkedHashMap: a HashMap that remembers the order of element insertion, which is preserved when retrieving with entrySet or other methods. In Entry, in addition to the node, there is a reference to the next and previous element.
TreeMap: a sorted map based on a red-black tree.

➤ Types and operation of synchronized collections

CopyOnWrite collections: all operations that modify the collection (add, set, remove) result in the creation of a new copy of the internal array. This ensures that an iterator’s passage through the collection will not throw a ConcurrentModificationException.

CopyOnWriteArrayList, CopyOnWriteArraySet

Scalable Maps: enhanced implementations of HashMap and TreeMap with better support for multithreading and scalability.

ConcurrentMap, ConcurrentHashMap, ConcurrentNavigableMap, ConcurrentSkipListMap, ConcurrentSkipListSet
Non-Blocking Queues: thread-safe and non-blocking implementations of Queue on linked nodes.

ConcurrentLinkedQueue, ConcurrentLinkedDeque
Blocking Queues:

BlockingQueue, ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue, BlockingDeque, LinkedBlockingDeque, TransferQueue, LinkedTransferQueue
[Source: habr.com/ru/company/luxoft/blog/157273/]

➤ How to sort a collection containing a complex class

Comparable: the class must implement the Comparable interface, specify the class as a generic (implements Comparable<Integer>), and implement the compareTo method.

Comparator: used when there is no access to the class, it’s necessary to create a new class, implement Comparator in it, override the compare method, then create an instance of this class and pass it to the collection constructor.

An anonymous class can also be used for on-the-fly implementation.

Set set = new TreeSet<String>(new Comparator<String>() {
public int compare(String i1, String i2) {
return i2.compareTo(i1);
}
});

// или

Comparator<String> comparator = (o1, o2) -> o1.compareTo(o2);
Set set = new TreeSet<String>(comparator);

// или

Collections.sort(list, comparator);

https://javarush.ru/groups/posts/1939-comparator-v-java
https://youtu.be/x4CUbW-K8E8

➤ What is the difference between ArrayList and LinkedList

ArrayList: a list based on an array.

LinkedList: a linked bidirectional list.

If elements are frequently added and removed, especially from the middle or the beginning of the list, then LinkedList is better; in other cases, ArrayList is preferable.

When created, the ArrayList has a DEFAULT_CAPACITY = 10, or a capacity can be specified in the constructor. When reached, the array size is increased by 1.5 times using the grow method.

int newCapacity = oldCapacity + (oldCapacity >> 1)

LinkedList stores information in an internal static Node class. It implements Queue and it is convenient to use it as a queue and, accordingly, there are queue methods — peek (pull and not delete), pool (pull and delete)

private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}

➤ What is a HashMap?

HashMap uses a hash table to store the map, providing fast execution times for get() and put() requests. Hashes are stored in a Bucket. You need to override equals() and hashCode(). Hash is an Integer to which the key is converted. First, the hash is compared, if it is the same, then the keys are compared, since a collision is possible when the hashes are the same, but the keys are not.

DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
DEFAULT_LOAD_FACTOR = 0.75f;
MAXIMUM_CAPACITY = 1 << 30 // 1_073_741_824 или половина max int

data storage class

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
// здесь хранится следующая нода если хеш совпадает
Node<K,V> next;
}

If in

transient Node<K,V>[] table;

no elements with passed to method

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

hash, executed

tab[i] = newNode(hash, key, value, null);

and if there is, then in Node<K,V> next of the Node class, the next element is added using

p.next = newNode(hash, key, value, null);

If the size is not enough, then

newCap = oldCap << 1 // или умножение на 2

The resulting hash code can be a huge numeric value, and the original array is conventionally designed for only 16 elements. Therefore, the hash code must be transformed into values ​​from 0 to 15 (if the array size is 16), additional transformations are used for this.
Java 8 uses balanced red-black Trees TreeNodes instead of linked lists after reaching a certain threshold (works similarly to TreeMap). This means that the HashMap initially stores the objects in a linked list, but after the number of elements in the hash reaches a certain threshold, it transitions to balanced trees, and then can be converted back to a normal node.

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
}

In Kotlin, you can also set with to

val map = mapOf("a" to 1, "b" to 2, "c" to 3)

if the Hash in the HashMap matches, a new entry is added to the element referencing the old entry, to view the entries in that element you will need to traverse the linked list of entries. It’s called the Chain Method

➤ How TreeMap works

When adding an element, first the first element is placed at the root of the root tree and becomes black, the following elements go if more than the top one then to the right and if less then to the left, and are attached to parent starting from root. The entry looks like this

➤ How TreeMap works

When adding an element, first the first element is placed at the root of the root tree and becomes black, the following elements go if more than the top one then to the right and if less then to the left, and are attached to parent starting from root. The entry looks like this

static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}

➤ What are the main annotations and methods in Room

@Entity(tableName = “users”): data class

@Entity(primaryKeys = [“firstName”, “lastName”]): composite primary key is used

@Entity(ignoredColumns = [“picture”]): ignore

@PrimaryKey(autoGenerate = true): primary key in data class

@ColumnInfo(name = “first_name”): column name, if it should be different from the variable name

@Ignore: Don’t use this field or this constructor

@Dao: abstract class or interface for working with a table

@Transaction: execute as one transaction, atomically

@Database(entities = [User::class], version = 1): abstract class that extends RoomDatabase

@Database(entities = [User::class], version = 1):
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name").build()

@TypeConverters(DateConverter.class):

class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time?.toLong()
}
}
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room.databaseBuilder(...)
.addTypeConverter(exampleConverterInstance)
.build()

@Query(“SELECT * FROM Users”): sql query

@Insert(onConflict = OnConflictStrategy.REPLACE): insert into table

@Delete: deleting from table

@Update: update

@DatabaseView: encapsulates the request in a class

@Fts3 or @Fts4: In Room 2.1.0 use FTS full text search

@AutoValue: https://github.com/google/auto/blob/master/value/userguide/index.md

@MapInfo(keyColumn = “userName”, valueColumn = “bookName”): returns the mapping between tables

@Embedded(prefix = “address”): take fields from the marked class and consider them table fields, prefix is needed if the names match

@Relation(parentColumn = “userId”, entityColumn = “userOwnerId”): will look for where parentColumn is equal to entityColumn, can also have a property associateBy = Junction(PlaylistSongCrossRef::class) where

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
val playlistId: Long,
val songId: Long
)

One-to-one dependency:

@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String,
val age: Int
)
@Entity
data class Library(
@PrimaryKey val libraryId: Long,
val userOwnerId: Long
)
data class UserAndLibrary(
@Embedded val user: User,
@Relation(parentColumn = "userId", entityColumn = "userOwnerId")
val library: Library
)
@Transaction
@Query("SELECT * FROM User")
fun getUsersAndLibraries(): List<UserAndLibrary>

One-to-many dependency:

@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String,
val age: Int
)
@Entity
data class Playlist(
@PrimaryKey val playlistId: Long,
val userCreatorId: Long,
val playlistName: String
)
data class UserWithPlaylists(
@Embedded val user: User,
@Relation(parentColumn = "userId", entityColumn = "userCreatorId")
val playlists: List<Playlist>
)
@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylists(): List<UserWithPlaylists>

Many-to-many dependency:

@Entity
data class Playlist(
@PrimaryKey val playlistId: Long,
val playlistName: String
)
@Entity
data class Song(
@PrimaryKey val songId: Long,
val songName: String,
val artist: String
)
@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
val playlistId: Long,
val songId: Long
)
data class PlaylistWithSongs(
@Embedded val playlist: Playlist,
@Relation(parentColumn = "playlistId", entityColumn = "songId", associateBy = Junction(PlaylistSongCrossRef::class))
val songs: List<Song>
)
data class SongWithPlaylists(
@Embedded val song: Song,
@Relation(parentColumn = "songId", entityColumn = "playlistId", associateBy = Junction(PlaylistSongCrossRef::class))
val playlists: List<Playlist>
)
@Transaction
@Query("SELECT * FROM Playlist")
fun getPlaylistsWithSongs(): List<PlaylistWithSongs>
@Transaction
@Query("SELECT * FROM Song")
fun getSongsWithPlaylists(): List<SongWithPlaylists>

synchronous requests:

coroutines: suspend fun loadUserById(id: Int): User

RX: public Single<User> loadUserById(int id);

LiveData: public ListenableFuture<User> loadUserById(int id);

Subscription:

coroutines: fun loadUserById(id: Int): Flow<User>

RX: public Flowable<User> loadUserById(int id);

LiveData: public LiveData<User> loadUserById(int id);

Pre-fill:

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
// или
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()

Migrations:

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.fallbackToDestructiveMigration()
.build()
// or
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.addMigrations(MIGRATION_1_2)
.build()
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
"PRIMARY KEY(`id`))")
}
}
// or
@Database(version = 2, entities = [User::class], autoMigrations = [AutoMigration (from = 1, to = 2)])

➤ What are the naming rules in Kotlin

Naming is very important for understanding the code, do not be lazy to name classes, methods and variables correctly according to what they mean and what they do
Classes and interfaces: capitalized in CamelCase
Methods and variables: written in camesCase with a small letter, except for constants
Constants: written in SNAKE_CASE in capital letters
Variable can start with _$ or a letter, but cannot start with a number, but can contain numbers
Interface implementations are often named Interface Name + Impl, for example InterfaceNameImpl
The names of variables starting with m and interfaces starting with I are outdated
Naming rules are described in the Java Code Conventions.
To transfer cases from one case to another in IDEA there is a convenient plugin “String Manipulation” https://plugins.jetbrains.com/plugin/2162-string-manipulation

➤ What are Inner Classes

In Kotlin, inner classes are classes that are declared inside another class. They have access to members of the outer class and can be used to implement design patterns such as “Strategy” or “Observer”.
Unlike Java, in Kotlin inner classes are static by default. That is, they do not have access to non-static members of the outer class and can be created without creating an instance of the outer class.
However, if the inner class is marked with the inner keyword, then it becomes non-static, and then it accesses the non-static members of the outer class and requires an instance of the outer class to create.
Example of an inner class in Kotlin:

class Outer {
private val outerField = "Outer field"

inner class Inner {
fun innerMethod() {
println("Accessing outer field: $outerField")
}
}
}

fun main() {
val outer = Outer()
val inner = outer.Inner()
inner.innerMethod() // Prints "Accessing outer field: Outer field"
}

➤ What are static classes in Java

A static class can only be an inner class; it can be inherited, just as it can inherit from any other class and implement an interface.
A static nested class is no different from a regular inner class, except that its object does not contain a reference to the outer class object that created it. To use static methods/variables/class, we do not need to create an object of that class. In regular inner classes, without an instance of the outer class, we cannot create an instance of the inner one.

public class Vehicle {
public static class Car {
public int km;
}
}
Vehicle.Car car = new Vehicle.Car();
car.km = 90;

https://javarush.ru/groups/posts/modifikator-static-java

➤ What does volatile mean?

The volatile keyword in Kotlin (and Java) is used to declare variables whose values can be changed by different threads. It ensures that when a variable is changed, the value is immediately written to and read from memory, rather than being cached in processor registers, which can lead to synchronization and change visibility issues.
The volatile keyword can be applied to variables of type Boolean, Byte, Char, Short, Int, Long, Float, Double, and reference types.

@Volatile
private var running: Boolean = false

In this example, the running variable will be updated in memory as soon as it is changed by any thread, and threads that use its value will see the most recent value in memory.
https://habr.com/ru/post/510454/
https://habr.com/ru/post/510618/

➤ What does synchronized mean?

synchronized: This is a keyword in Kotlin (and Java) that is used to synchronize access to shared resources in multi-threaded applications. When multiple threads try to access a shared resource at the same time, problems can arise, such as ambiguous state of the resource or corruption of the resource. synchronized avoids these problems by ensuring that only one thread can access a shared resource at a time.

synchronized(lock) {
// block of code that accesses the shared resource
}

@Synchronized
fun getCounter(): Int {
return counter
}

Coroutines cannot be synchronized because synchronized is a keyword in Java that is used to synchronize access to shared resources between multiple threads. Instead, to synchronize access to shared resources between coroutines in Kotlin, you should use other synchronization mechanisms, such as atomic variables, locks, or mutexes. For example, you can use the mutex from the Kotlin standard library:

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex

val mutex = Mutex()

fun main() = runBlocking {
launch {
mutex.withLock {
// code to be executed synchronously
}
}
}

This example uses the Mutex mutex from the Kotlin standard library, which allows you to lock access to a shared resource inside a withLock block. This ensures that the code inside the withLock block will only be executed by one coroutine at any given time.

➤ What is Generic?

With their help, you can declare classes, interfaces and methods, where the data type is specified as a parameter, for example <Integer>, in this case there is no need to do a type cast (Integer). If you use Object in Java or Any in Kotlin as a generic, you can use any data type, but then you need to cast to the desired one using parentheses in Java or “as” in Kotlin.
Legacy Java code does not use generics.
Generics are visible only at the compilation stage, then casting is carried out to the type specified in the generic.
<? extends Object>: Generics can also be masks, for example Collection<? extends Object> or List<? super String>
<T>: Letters can also be generics. In this example, we limited T to descendants of the NewClass class and the Comparable interface, so T will have the same properties.

class NewClass<T extends NewClass & Comparable> {
T variable;
NewClass method(T variable) {
// Comparable method
variable.compareTo(1);
// NewClass method
variable.method(new NewClass());
List<T> list = new ArrayList<>();
List<NewClass> list2 = new ArrayList<>();
list.add(variable);
list2.add(variable);
return list.get(0);
}
}

Kotlin also has:
Source<out T>: indicates that the type T is covariant, that is, the type List<String> is a subtype of the type List<Any>. The <out T> type projection allows the type T to be used only as a method return type or property value type, but not as a method parameter type or property value type.

interface Producer<out T> {
fun produce(): T
}

Source<in T>: indicates that the type T is contravariant, that is, the type List<Any> is a subtype of the type List<String>. The <in T> type projection allows the type T to be used only as a method parameter or property value type, but not as a method return type or property value type.

interface Consumer<in T> {
fun consume(item: T)
}

<reified T>: Typically, during program execution, information about the types of arguments is removed, and you cannot access the types used as arguments to a generic function. However, by using the reified keyword, you can access the type of the argument at runtime.

inline fun <reified T> getTypeName() = T::class.simpleName

fun main() {
println(getTypeName<String>()) // prints "String"
println(getTypeName<Int>()) // prints "Int"
}

n this example, we have defined a generic function getTypeName() that uses the reified keyword to obtain the name of a class of type T at run time. We call this function with different types and it returns the class name for each type.
The reified keyword can only be used with generic functions, not with classes or interfaces. Additionally, a generic type declared using the reified keyword can only be used in a context where the type is explicitly specified, such as when calling another function.
<*>: Used to specify an undefined argument type when using generic types.
When you use a generic type in Kotlin code, you can specify the type of the argument in angle brackets. But sometimes you may want to use a generic type but not specify a specific argument type, for example if you want to use a type that can contain objects of different types.
In this case, you can use the <*> character instead of the argument type. For example:

val list: List<*> = listOf("foo", 42, Any())

In this example, we created a list of type List<*>, which can contain objects of any type. We added three objects of different types to the list: the string “foo”, the number 42, and an object of type Any(). Now list contains all three objects.
The <*> character can also be used instead of an argument type when creating a generic class object, for example:

val map: Map<String, *> = mapOf("foo" to 42, "bar" to "baz")

Function<*, String>: means Function<in Nothing, String>;
Function<Int, *>: means Function<Int, out Any?>;
Function<*, *>: means Function<in Nothing, out Any?>
https://kotlinlang.ru/docs/reference/generics.html

➤ What methods does the Object class in Java and Any in Kotlin have?

wait(), notify(), notifyAll(): three methods from the set for multithreading. I’m not sure that I’ll talk about this in this course, since it’s a rather broad and complex topic. However, even from the names one can understand that these methods are capable of making some people wait (wait) and, conversely, awakening them (notify).
getClass(): Get the class of an object at runtime. Mainly used for reflection.
clone(): Get an exact copy of an object. Not recommended for use. It is often recommended to use a copy constructor.
equals(): Compares two objects.
hashcode(): The numeric representation of the object. The default value is an integer address in memory.
toString(): Returns the string representation of the object. By default, returns class_name@hashcode in hexadecimal. If hashcode is not overridden, the default value will be returned.
finalize(): If an object interacts with some resources, for example opening an output stream and reading from it, then such a stream must be closed before removing the object from memory. To do this, in the Java language it is enough to override the finalize() method, which is called in the Java runtime immediately before deleting an object of this class. In the body of the finalize() method, you need to specify the actions that must be performed before destroying the object. The finalize() method is called only immediately before garbage collection
Any class in Kotlin has methods
equals(),
hashCode(),
toString()
https://www.examclouds.com/ru/java/java-core-russian/class-object

➤ What is Enum in Java

A special class representing an enumeration
In its simplest implementation it looks like this

enum Size { SMALL, MEDIUM, LARGE }

its functionality can be extended

enum Size {
SMALL("small"),
MEDIUM("medium"),
LARGE("large") {
@Override
String getSize() {
return size + ", this is maximum size";
}
};
String size;
Size(String size) {
this.size = size;
}
String getSize() {
return size;
}

https://youtu.be/ll14SKsQScE

➤ What is the priority in Java when converting primitive types?

example for byte
byte ➜ short ➜ int ➜ long ➜ float ➜ double ➜ Byte() ➜ Object() ➜ Byte()… ➜ byte… ➜ short… ➜ int… ➜ long… ➜ float… ➜ double… ➜ Object()…

➤ What is typecasting/type/caste conversion

There are explicit and implicit conversions
Automatic widening conversion possible
byte -> short -> int -> long
int -> double
short -> float -> double
char -> int
Possible conversion with loss of information
int -> float
long -> float
long -> double
Explicit conversion with loss of data
int a = 258;
byte b = (byte) a; // 2
double a = 56.9898;
int b = (int) a; // 56
Transformations during operations
if one of the operands of the operation is of type double, then the second operand is converted to type double
if the previous condition is not met, and one of the operands of the operation is of type float, then the second operand is converted to type float
if the previous conditions are not met, one of the operands of the operation is of type long, then the second operand is converted to type long
otherwise all operands of the operation are converted to type int
Kotlin has
as: regular non-type-safe casting
as?: type-safe cast, returns null on failure
is: is this type
!is: is not this type

➤ What is the difference between StringBuffer and StringBuilder

StringBuffer and StringBuilder are used for operations with text data, StringBuffer is synchronized and thread safe

➤ What is “instanceof” in Java or “is” / “!is” in Kotlin

used to check if an object is an instance of a given class, returns boolean

➤ What are they for and how to create annotations in Java

Annotations in Java are labels in code that describe metadata for a function/class/package.
Annotations are classified by storage type
SOURCE: used only when writing code and ignored by the compiler
CLASS: preserved after compilation, but ignored by the JVM
RUNTIME: saved after compilation and loaded by JVM
The type of object above which is indicated
ANNOTATION_TYPE: different annotation
CONSTRUCTOR: class constructor
FIELD: class field
LOCAL_VARIABLE: local variable
METHOD: class method
PACKAGE: package description package
PARAMETER: method parameter public void hello(@Annontation String param){}
TYPE: indicated above the class
Java SE 1.8 standard language library provides us with 10 annotations
Override
Retention: SOURCE; Target: METHOD.
Indicates that the method over which it is written is inherited from the parent class.
Deprecated
Retention: RUNTIME; Target: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE.
Indicates methods, classes or variables that are “obsolete” and may be removed in future versions of the product.
SuppressWarnings
Retention: SOURCE; Target: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE
Disables the output of compiler warnings that concern the element above which it is specified. Is the SOURCE annotation indicated above fields, methods, classes.
Retention
Retention: RUNTIME; Target: ANNOTATION_TYPE;
Specifies the “storage type” of the annotation above which it is specified. Yes, this annotation is even used for itself.
Target
Retention: RUNTIME; Target: ANNOTATION_TYPE;
Specifies the type of object over which the annotation we create can be indicated. Yes, and she is also used for herself.
In Java, annotations are created using @interface, in Kotlin using annotation class

@interface AnnotationName {
int getResult() default 1;
String value();
}

@AnnotationName("value")
public class Main {
@AnnotationName("value")
String text;
@AnnotationName(value = "value", getResult = 1)
public static void main(String[] args) {
}
public static void testAnnotation(Object object) throws Exception {
boolean haveAnnotation = !object.getClass().isAnnotationPresent(AnnotationName.class);
Class<Main> demoClass = Main.class;
AnnotatedElement annotatedElement = demoClass;
Annotation[] annotations = annotatedElement.getAnnotations();
Method method = demoClass.getMethod("testAnnotation");
annotations = method.getAnnotations();
}
}

➤ What is reflection in Kotlin

Reflection in Kotlin: is a mechanism that allows programs to examine and modify their structure and behavior at runtime.
With reflection in Kotlin, you can access information about classes, interfaces, fields, methods, and constructors during program execution. This can be useful in cases where you don’t know the class structure in advance but want to process it dynamically.
For example, using reflection in Kotlin, you can create new objects, call methods, set field values, and even load new classes during program execution. However, you need to be careful when using reflection, as it can lead to poor performance and poor code readability.

public class Main {
public static void main(String[] args) throws Exception {
ClosedClass closedClassNewInstance = new ClosedClass();
Class closedClass = closedClassNewInstance.getClass();
closedClass = ClosedClass.class;
closedClass = Class.forName("com.company.ClosedClass");
String className = closedClass.getName();
closedClassNewInstance = (ClosedClass) closedClass.newInstance();
Constructor[] constructors = closedClass.getConstructors();
constructors = closedClass.getDeclaredConstructors();
Method[] methods = closedClass.getMethods();
Parameter[] parameters = constructors[0].getParameters();
String name = parameters[0].getName();
String type = parameters[0].getType().getName();
Method method = closedClass.getMethod("method");
method.invoke(closedClass);
Field field = closedClass.getField("variable");
field.setAccessible(true);
field.setInt(closedClass, 1);
}
}
class ClosedClass {
int variable;
void method(){}
}

➤ What problems can there be with multithreading in Kotlin

Race condition: a situation in which the outcome of a program depends on which threads execute faster or later.

var count = 0

fun main() {
Thread {
for (i in 1..100000) {
count++
}
}.start()

Thread {
for (i in 1..100000) {
count++
}
}.start()

Thread.sleep(1000)

println("Count: $count")
}

This example creates two threads, each of which increments the count variable by 1 the same number of times. After these threads execute, the main thread prints the value of the count variable. However, since two threads can run in parallel, the result depends on which thread finishes first and may be unexpected.

For example, in one run of the program the result may be 199836, and in another — 200000. This occurs because the count variable is not used atomically (it is not protected by synchronization mechanisms), and two threads can change its value simultaneously. As a result, variable values may be overwritten and the final value may be less than expected.

To avoid Race condition in such cases, it is necessary to use synchronization and protection mechanisms, such as locks and atomic variables, which help ensure the correct execution of operations and avoid unexpected results.

Deadlock: Occurs when two or more threads block while waiting for each other to release resources needed to continue execution.
Misuse of synchronized: synchronized can be used incorrectly, which can lead to incorrect execution order or blocked threads.
To avoid these problems, Kotlin can use synchronization tools such as mutex, lock, atomic variables and others, as well as take advantage of modern multi-threaded programming practices such as using immutable data structures and limiting changes to shared data only within critical sections.

class Resource(private val name: String) {
@Synchronized fun checkResource(other: Resource) {
println("$this: checking ${other.name}")
Thread.sleep(1000)
other.checkResource(this)
}
}

fun main() {
val resource1 = Resource("Resource 1")
val resource2 = Resource("Resource 2")

Thread {
resource1.checkResource(resource2)
}.start()

Thread {
resource2.checkResource(resource1)
}.start()
}

This example creates two Resource objects, each synchronized using the @Synchronized annotation. Next, two threads are created, each of which calls the checkResource() method on different resources in different orders. So, if the first thread blocks resource1 and the second thread blocks resource2, then both threads will wait for each other and will not be able to complete execution, resulting in a Deadlock. Deadlock example in Kotlin can be avoided if communication between threads occurs using shared locks, such as using synchronized or lock() from the Kotlin standard library. You can also use the wait() and notify() methods to control threads and avoid blocking.

Livelock: A situation in which two or more threads continue to perform actions to avoid blocking, but are unable to complete their work. As a result, they are in an endless loop, consuming more and more resources without doing any useful work.

data class Person(val name: String, val isPolite: Boolean = true) {
fun greet(other: Person) {
while (true) {
if (isPolite) {
println("$name: After you, ${other.name}")
Thread.sleep(1000)
if (other.isPolite) {
break
}
} else {
println("$name: No, please, after you, ${other.name}")
Thread.sleep(1000)
if (!other.isPolite) {
break
}
}
}
}
}

fun main() {
val john = Person("John", true)
val jane = Person("Jane", false)

Thread {
john.greet(jane)
}.start()

Thread {
jane.greet(john)
}.start()
}

This example creates two Person objects, each of which can be polite or unpolite depending on the value of the isPolite property. Then two threads are created, each calling the greet() method on a different object. The greet() method uses a while loop to check whether another object is polite and continue or stop depending on that.

If both targets are polite, they will alternately ask each other to leave first and will not be able to finish their conversation. If both targets are not polite, they will refuse to leave first and will also be unable to complete the conversation. This way, both threads will continue to run in an infinite loop without doing any useful work, which is an example of Livelock.

The Livelock example can be avoided, for example, by using timers and limiting the execution time of operations so that threads can complete their work and avoid an infinite loop. You can also use synchronization and locks to ensure correct communication between threads.

https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0 %B3%D0%BE%D0%BD%D0%BA%D0%B8
https://javarush.com/groups/posts/296-chto-takoe-deadlock-

➤ What are wrappers in Java

Wrappers in Java are classes that wrap primitive data types such as int, char, boolean, etc. so that they can be used as objects. These classes are in the java.lang package and have the following names:

Integer() for int
Character() for char
Boolean() for boolean
Short() for short
Long() for long
Double() for double
Float() for float
Byte() for byte

Each of these classes contains methods and fields that allow you to work with primitive data types as objects. For example, the Integer class contains methods for converting numbers to strings, comparing numbers, etc.
Wrappers are also used to pass primitive data types as parameters to methods that expect objects. For example, if a method expects a parameter of type Object and you want to pass it an integer, you can create an Integer object, wrap the number in it, and pass that object to the method.
Another advantage of wrappers is the ability to use null values. Primitive types cannot be null, but wrappers can. If you are not sure whether you will have a value for a variable or not, you can use a wrapper to avoid errors.
In Kotlin, wrappers are not used, instead the compiler decides when to use a primitive type and when to use a reference one

➤ What are the transition operators?

return: defaults to returning from its closest surrounding function or anonymous function

break: ends the loop

continue: continues the execution of the loop from its next step, without processing the remaining code of the current iteration
Any expression in Kotlin can be marked with a label.

loop@ for (i in 1..100) {
for (j in 1..100) {
if (true)
break@loop
}
}
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit
print(it)
}
}
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach
print(it)
}
}
return@a 1 // return 1 in the @a label rather than returning the expression with the label (@a 1)

➤ In what situations does Memory leak occur?

due to static fields, through unclosed resources, incorrect implementations of equals() and hashCode(), internal classes that refer to external classes, incorrect use of lazy in Kotlin, context leaks, etc.
https://topjava.ru/blog/java-memory-leaks
An example of code where a memory leak can occur:

class MyActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)

val button = findViewById<Button>(R.id.my_button)
var data: String = "Initial data"

button.setOnClickListener {
// Here the lambda grabs a reference to data
// and will continue to use it even after
// how the data was destroyed.
Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
}

// Suppose we want to free data from memory,
// to prevent a memory leak.
// We do this by setting data to null.
data = null // This will not remove the data reference from the lambda.

}
}

➤ What is Stack (stack) and Heap (heap) in Java

For optimal application performance, the JVM divides memory into a stack area and a heap area. The stack operates according to the LIFO (last in, first out) scheme. Whenever a new method is called that contains primitive values or object references, a block of memory is allocated at the top of the stack for them. The heap is used for objects and classes. New objects are always created on the heap, and references to them are stored on the stack. The primitive field of a class instance is stored on the heap. The heap is used by all parts of the application while the stack is used by only one thread of execution of the program.

Stack memory contains only local variables of primitive types and references to objects on the heap. Objects on the heap are accessible from anywhere in the program, while stack memory cannot be accessed by other threads. If the stack memory is full, then the Java Runtime throws java.lang.StackOverflowError, and if the heap memory is full, then the exception java.lang.OutOfMemoryError: Java Heap Space is thrown.

Each thread running in the Java Virtual Machine has its own stack. The stack contains information about which methods the thread called. As soon as a thread executes its code, the call stack changes. A thread’s stack contains all the local variables for each method that is executed. A thread can only access its own stack.

When a method is called, the JVM creates a new block on the stack called a “frame” or “stack frame”. This block stores local variables of the method, as well as references to objects passed to the method as arguments.
Each new method call adds a new frame to the stack, and method completion removes the last frame from the stack. This mechanism is called a “stack machine”.

Additionally, the stack is used to store return information from methods. When a method is called, the return address is pushed onto the stack so that the JVM knows where to return after the method completes.

The stack is limited in size and can cause a stack overflow error if too many frames are created on the stack. This can happen, for example, if a method calls itself recursively too many times. In a standard JVM configuration, the stack size can range from a few megabytes to several tens of megabytes, depending on the JVM version and operating system. However, the stack size can be changed at JVM startup using the -Xss option. For example, to set the stack size to 1 megabyte, you need to start the JVM with the -Xss1m option.

In a standard JVM configuration, the heap size can range from several hundred megabytes to several gigabytes, depending on the JVM version and operating system. However, the heap size can be changed at JVM startup using the -Xms and -Xmx options. The -Xms option sets the initial heap size, and the -Xmx option sets the maximum heap size. For example, to set the initial heap size to 256 megabytes and the maximum heap size to 1 gigabyte, you need to start the JVM with the parameters -Xms256m -Xmx1g.

Local variables are invisible to all other threads except the thread that created them. Even if two threads are executing the same code, they will still create local variables of that code on their own stacks. Thus, each thread has its own version of each local variable.

All local variables of primitive types (boolean, byte, short, char, int, long, float, double) are completely stored on the thread stack and are not visible to other threads. One thread can pass a copy of a primitive variable to another thread, but cannot share a primitive local variable.

The heap contains all the objects created in your application, regardless of which thread created the object. This also includes versions of objects of primitive types (for example, Byte, Integer, Long, etc.). It doesn’t matter whether an object was created and assigned to a local variable or created as a member variable of another object, it is stored on the heap.

A local variable can be of a primitive type, in which case it is stored entirely on the thread’s stack.
A local variable can also be a reference to an object. In this case, the reference (local variable) is stored on the thread’s stack, but the object itself is stored on the heap.

An object can contain methods, and these methods can contain local variables. These local variables are also stored on the thread’s stack, even if the object that owns the method is stored on the heap.

An object’s member variables are stored on the heap along with the object itself. This is true both if the member variable is of a primitive type and if it is an object reference.

Static class variables are also stored on the heap along with the class definition.

Objects on the heap can be accessed by all threads that have a reference to the object. When a thread has access to an object, it can also access that object’s member variables. If two threads call a method on the same object at the same time, they will both have access unit to the object’s member variables, but each thread will have its own copy of the local variables.

ublic static void main(String[] args) {
int x = 10; // variable of type int is saved on the stack
String str = "Hello World!"; // object of type String is stored on the heap
System.out.println(str);
}

In this example, the int variable x is stored on the stack because it is a primitive data type. The variable str of type String is an object and is stored on the heap.
When the program runs, the main method is pushed onto the stack, a variable x is created and assigned the value 10. A String object is then created containing the string “Hello World!” A reference to this object is stored in the str variable. When the println method is called, the value of the str object reference is passed to the method and it prints the string “Hello World!” to the console.
After the main method executes, all variables are removed from the stack, and the String object continues to exist on the heap as long as it is referenced from other parts of the program.
In addition to the stack and heap, Java has a persistent data storage area (PermGen or Metaspace depending on the version of Java). This area contains class metadata, information about methods, variables and other data related to the program structure itself. In Java 8 and higher, PermGen is replaced by Metaspace.
The program code is compiled into bytecode, which is stored in a .class file. When a program runs, the bytecode is loaded into memory and interpreted by the Java Virtual Machine (JVM).

https://habr.com/ru/post/84165/
https://topjava.ru/blog/stack-and-heap-in-java
https://habr.com/ru/post/510454/

➤ What are JDK, JRE and JVM

JDK (Java Development Kit): This is a set of tools that developers use to create Java applications. The JDK includes a Java compiler, Java class libraries, utilities for developing and debugging Java applications, and other tools. JDK is a complete setup for developing Java applications.

JRE (Java Runtime Environment): It is a Java runtime environment that is used to run Java applications. The JRE includes the Java Virtual Machine (JVM), Java class libraries, and other components needed to run Java applications. The JRE does not contain tools for developing Java applications.

JVM (Java Virtual Machine): It is a virtual machine that allows Java code to run on the computer. The JVM translates Java bytecode into machine code that can be executed on a specific platform. The JVM is a key component of the Java platform as it provides the ability to write Java code once and run it on any platform where the JVM is installed.

Each component is an important part of the Java platform, and they interact with each other. Developers use the JDK to create Java applications, and then the JRE is used to run those applications, using the JVM to execute the Java code.

https://javadevblog.com/chto-takoe-jdk-jre-i-jvm-v-java.html

➤ What values are default variables initialized to?

The default value depends on the variable type:
For numeric types (byte, short, int, long, float, double), the default value is 0.
For the char type, the default value is ‘\u0000’ (the ‘NUL’ character).
For type boolean, the default value is false.
For reference types (any class, interface, array), the default value is null.

➤ What is the syntax for querying a SQL database

SELECT field1, field2 FROM table name WHERE clause GROUP BY ...
INSERT INTO table_name SET field1=value1, field2=value2, field3=value3
UPDATE table_name SET field1=value1, field2=value2, field3=value3 WHERE condition_on_to_select_rows
DELETE FROM table_name WHERE condition
SELECT COUNT(field) FROM table_name WHERE condition

http://old.code.mu/sql/select.html

➤ What is Typecasting in Kotlin?

Explicit or automatic (implicit) conversion from one data type to another, using a parenthesized data type in Java or “as” in Kotlin, or implicit conversion with type expansion without data loss, and also using the “instanceof” check in Java or “is” ” in Kotlin. There is also a safe caste “as?” which will return null if the cast fails
https://metanit.com/java/tutorial/2.2.php
https://ru.wikibooks.org/wiki/Java/%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8 %D0%B5_%D1%82%D0%B8%D0%BF%D0%BE%D0%B2

➤ What is the ternary selection operator?

"condition ? if true : if not true"

same as if-else

➤ Is it possible to narrow the access level/return type when overriding a method?

no, because this would lead to a violation of Barbara Liskov’s substitution principle. Expansion of the access level is possible.

➤How clone works in Kotlin and Java

If you do

NewClass newClass = new NewClass();
NewClass newClass2 = newClass;

then newClass and newClass2 will be the same object in memory, which is referenced by the 2 variables newClass and newClass2, since new was called once.
To create 2 objects you either need to call new on each variable or
implement the Clonable interface in the cloned class
override the clone() method, inside which call super.clone()
call clone() method

NewClass newClass = new NewClass();
NewClass newClass2 = newClass.clone();

Now newClass and newClass2 will be different objects in memory, but only primitives will be cloned, not reference types
If there are reference types in the class, they also need to be cloned using clone(), for this they also need to implement Сlonable, override clone, and then call their clone() method inside the parent class’s clone() method override.

➤ What is the life cycle of an activity, fragment, View:

ACTIVITY:
onCreate()
onStart()
onResume()

onPause()
onStop()
onDestroy() (can be called immediately if finish() is used in the activity callback onCreate())

FRAGMENT:
onCreate()
onCreateView()
onViewCreated()
onViewStateRestored()
onStart()
onResume()

onPause()
onStop()
onDestroyView()
onDestroy()
onDetach()

VIEW:
Constructor: When creating a custom view, you must first override the constructor that initializes the View, the necessary calculations can be carried out in it
OnAttachedToWindow(): Called when a View is attached to a window using addView(View)
measure()
onMeasure(int widthMeasureSpec, int heightMeasureSpec): Called to determine the dimensions of the View
layout(): calls setFrame(), which calls onSizeChanged()
onLayout(): Allows you to assign size and position to child View components
dispatchToDraw()
draw()
onDraw(Canvas canvas): After the dimensions and positions have been calculated in the previous methods, the View can now be drawn based on them. It generates a Canvas object on which the background will be drawn, and a Paint object, which is responsible for the color. Never create objects in onDraw() as it is called multiple times.

requestLayout(): This method is called when something has changed and invalidated the layout of this View
invalidate(): calling this method invalidates the entire View
at Widget:
onEnabled(): Called when the user adds the first widget instance to the home screen. When adding new instances of the same type, onEnabled() will not be called. In this method, you should perform initialization operations common to all widget instances.
onUpdate(): Called when a widget instance is added. Also called periodically at the time interval that was specified in AppWidgetProviderInfo. This method is used to initialize and update state.
onAppWidgetOptionsChanged(): Called when added and when resized.
onDeleted(): Called when a widget instance is deleted.
onDisabled(): Called when all widget instances are removed. This method should release the resources created during initialization in the onEnabled() method.

VIEWMODEL:
The ViewModel only has a class constructor and an onCleared method, which is called when the application is put into the background and the application process is killed to free up system memory

https://proandroiddev.com/the-life-cycle-of-a-view-in-android-6a2c4665b95e
https://metanit.com/java/android/2.1.php
https://metanit.com/java/android/8.3.php

➤ How can you pass data to a fragment

Parameters are passed to the Fragment constructor via Bundle, using the Fragment.setArgument(Bundle) method. The passed bundle can be retrieved via Fragment.getArguments() in the corresponding fragment lifecycle method.
A common mistake is passing data through a custom constructor. It is not recommended to use non-default fragment constructors, because the fragment can be destroyed and recreated due to configuration changes (for example, when the screen is rotated).
Using the setArguments/getArguments method pair ensures that when the Bundle is recreated, it will be serialized/deserialized and the data will be restored.
Sample code:

// Send the result to another fragment
val someData = "123"
SomeFragmentManager?.setFragmentResult(
BUNDLE_KEY,
bundleOf(STRING_KEY to someData)
)

// get the result in another fragment
SomeFragmentManager?.setFragmentResultListener(
BUNDLE_KEY, viewLifecycleOwner
) { _, bundle ->
bundle.getString(STRING_KEY)?.let(viewModel::setString)

➤ What is LiveData

LiveData: This is a class that is designed to provide communication between application components that can be updated and used in different threads. LiveData allows you to create objects that store data and automatically notify all subscribers when that data changes. LiveData is one of the components of the Jetpack architecture, which allows you to create applications based on the principles of separation of duties and simplification of working with data. LiveData has the following features:
It can update automatically when data changes.
It provides data type safety.
It can be used in conjunction with the Android lifecycle to avoid memory leaks and errors associated with running on other threads.
It can be used to create reactive user interfaces that update automatically when data changes.
LiveData provides methods that let you subscribe to and unsubscribe from data changes, as well as methods that let you get the current value of the data. LiveData also has methods that allow you to update data and notify all subscribers when data changes.
There is also SingleLiveEvent, an extension to the LiveData class in Android, which is designed to pass events in one direction from ViewModel to View. It allows you to handle only one event, ignoring all subsequent events that may occur until the View receives an update.
SingleLiveEvent can be useful, for example, for handling events such as button clicks that should only be executed once, or for handling errors that should only be displayed once.
The SingleLiveEvent class contains a setValue() method, which is used to set a new value, and a call() method, which allows you to raise an event without passing a new value. In this case, SingleLiveEvent automatically deletes its observer after it receives a value. Its standard implementation:

class SingleLiveEvent<T>() : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)

constructor(value: T) : this() {
setValue(value)
}

@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Timber.w("Multiple observers registered but only one will be notified of changes.")
}
super.observe(owner) { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}

@MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}

@MainThread
fun call() {
value = null
}
}

➤ What is Android Data Binding

Android Data Binding: A library introduced in Android in version 1.3 that allows you to bind user interface (UI) elements to data in an application using a declarative approach. It allows you to avoid writing a lot of the same type of code to associate user interface elements with data, which makes application development easier.
With Data Binding, you can use XML markup to describe the user interface, and bind UI elements to data in the view model using bindings, without having to explicitly write code for binding and event handling. This allows you to reduce the amount of code and reduce the chance of errors during development.
Data Binding also supports two-way binding, which allows you to update data in UI elements when data in the view model changes, and vice versa.
An example of using Data Binding to bind a TextView to a username:
XML markup:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
</LinearLayout>
</layout>

View model code:

public class User {
public String name;
public User(String name) {
this.name = name;
}
}

This example binds a TextView to the “name” field of a User object, and will update the text in the TextView when the value of the “name” field changes.
https://stfalcon.com/ru/blog/post/faster-android-apps-with-databinding

➤ What is Android ViewModel

Android ViewModel: This is a component of the Jetpack architecture that helps save and manage data of UI components (Activity, Fragment) during screen rotations, application re-creation and other changes in the application lifecycle.
ViewModel is part of the Model-View-ViewModel (MVVM) architectural pattern and is typically used in conjunction with LiveData to provide up-to-date data for the user interface.
ViewModel has only one lifecycle method — OnCleared()
Android SharedViewModel: This is a component of the Jetpack architecture that allows data to be shared between two or more related UI components (Activity, Fragment) using the same ViewModel instance.
SharedViewModel is typically used in cases where two or more UI components need to access the same data but are not directly related to each other. For example, in a case where we have a list of elements in one fragment and the element details are displayed in another fragment which is in another Activity.
https://habr.com/ru/post/334942/

➤ What elements does an Android application consist of?

Activity: This is the main user interface component that typically represents a single screen of an application. Each activity has its own lifecycle and can launch other activities.

Fragment: These are components that can be used inside activities to create a more complex user interface. Fragments also have their own life cycle and can be used to create reusable components.

Service: These are components that perform background operations in the application without user interaction. Services can run in the background even after the user has closed the application.

Broadcast Receiver: These are components that allow an application to receive messages from the system or other applications even when the application is not active. Broadcast receivers can be used to respond to system events such as changes in network status or battery charging.

Content Provider: These are components that allow an application to store and exchange data with other applications or the system. Content providers can be used to access data stored in a database or on a remote server.

AndroidManifest.xml: This is a file that contains information about the application components and its settings. The application manifest also defines the permissions required to access various device features.

https://sites.google.com/site/interviewknowages/-android

➤ What are Android Broadcast Receivers

Android Broadcast Receivers: These are Android components that allow an application to receive system and user messages (broadcast) from other applications, as well as from the system itself.
The Broadcast Receiver listens and filters messages, and if an event matches the specified filters, it runs the code associated with that event.
Examples of such events may include a change in network state, a change in battery level, receiving a message from another application, etc.
Broadcast Receiver can be used to perform some task on the device, such as notifying the user about changes in the device’s state or running tasks in the background.
To use Android Broadcast Receiver, you need to create a class that will extend the BroadcastReceiver class and override the onReceive() method, which will be called when a broadcast message is received.

class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// processing the received message
}
}

Next you need to register this Broadcast Receiver in the AndroidManifest.xml file:

<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_NAME" />
</intent-filter>
</receiver>

In this example, MyBroadcastReceiver will process messages with the action “android.intent.action.ACTION_NAME”. When such a message is received, the onReceive() method of this Broadcast Receiver will be called.
You can also register a Broadcast Receiver dynamically in code using the registerReceiver() and unregisterReceiver() methods of the Context class.
https://www.fandroid.info/android-broadcast-receivers-for-beginners/

➤ What are the types of links in Java?

Strong Reference: This is an object reference that guarantees that the object will not be removed from memory while there is at least one such reference to it. When an object is under the control of a strong reference, its life cycle is determined by the life cycle of the reference. If the reference is not used, then the object is also not removed from memory, which can lead to a memory leak.

String title = “hello”;

SoftReference: Objects created via SoftReference will be collected in case the JVM requires memory. That is, there is a guarantee that all soft reference objects will be collected before the JVM throws an OutOfMemoryError. SoftReference is often used for memory-intensive caches.

class MyClass {
private var softRef: SoftReference<HeavyObject>? = null

fun doSomething() {
var heavyObj = softRef?.get()
if (heavyObj == null) {
heavyObj = HeavyObject()
softRef = SoftReference(heavyObj)
}
// use heavyObj here
}
}

class HeavyObject {
// ...
}

This example uses a SoftReference to store a reference to a HeavyObject, which can take up a lot of memory. The doSomething() method checks whether the SoftReference already exists and whether the HeavyObject is saved. If the object has not yet been created or has already been removed from memory, a new object is created and a reference to it is stored using SoftReference. Otherwise, if the HeavyObject already exists in memory and there is a reference to it via SoftReference, then the reference to it is returned for further use.
It’s important to note that SoftReference does not guarantee that an object will always be available in memory. If the system runs out of memory and there is not enough space to store the HeavyObject, the object can be deleted and the SoftReference will be automatically cleared. The next time you try to access the object, a new object will be created and a reference to it will be stored in SoftReference.

WeakReference: weaker than SoftReference, does not save the object from finalization, even if there is a sufficient amount of free memory. As soon as there are no strong and soft references left to the object, it can be finalized. Used for caches and for creating chains of interconnected objects.

private var activityNavController: WeakReference<NavController>? = null

// или

var person: Person? = Person("John")
val weakReference = WeakReference(person)
println("Before garbage collection: $weakReference")
person = null
System.gc()
println("After garbage collection: $weakReference")

This example creates an instance of the Person class and then creates a weak reference to it using WeakReference. After this, the reference to the original object is removed and garbage collection is invoked using the System.gc() method. At the end, it will display information about the reference to the Person object before and after garbage collection.
Note that after garbage collection, the Person object reference becomes null because the original object has been removed from memory. The reference to the object via WeakReference also becomes null, which means that the object has been deleted.

PhantomReference: Objects created via PhantomReference are destroyed when the GC determines that the referenced objects can be deallocated. This type of reference is used as an alternative to finalization for more flexible resource release.

class MyObject {
// resources associated with the object
}

val phantomReferenceQueue = ReferenceQueue<MyObject>()
val myObject = MyObject()
val phantomReference = PhantomReference(myObject, phantomReferenceQueue)

// perform some actions

// check if the object has been deleted from memory
if (phantomReferenceQueue.poll() != null) {
// release resources associated with the object
}

Here we have created a MyObject and placed it in the PhantomReference. We then perform some actions and, if the object has been removed from memory, we free the resources associated with it. Note that we created a ReferenceQueue to track the deletion of an object.
https://javadevblog.com/tipy-ssy-lok-v-java-strongreference-weakreference-softreference-i-phantomreference.html
https://youtu.be/yBcV-zcCifE

➤ What is JOIN in SQL

The JOIN operator is used to join two or more tables. A table join can be internal (INNER) or outer (OUTER), and an outer join can be left (LEFT), right (RIGHT) or full (FULL).
http://www.skillz.ru/dev/php/article-Obyasnenie_SQL_obedinenii_JOIN_INNER_OUTER.html
Using Join, you can join tables in Room in a way that is alternative to Embedded and Relation; queries are more difficult to write using SQL syntax, but it works much faster than automatic joins via Embedded and Relation

users
id | name
----|-----
1 | Alice
2 | Bob
3 | Charlie

orders
id | user_id | product
----|---------|--------
1 | 1 | iPhone
2 | 2 | iPad
3 | 1 | MacBook

SELECT orders.id, orders.product, users.name
FROM orders
INNER JOIN users
ON orders.user_id = users.id;

id | product | name
----|----------|------
1 | iPhone | Alice
2 | iPad | Bob
3 | MacBook | Alice

➤ What is Comparator

The Comparator class is a functional interface that is used to compare objects. In Kotlin, you can use lambda expressions to create instances of the Comparator class.
For example, if we have a Person class with name and age properties, we can create a Comparator to sort the Person list by age like this:
https://www.examclouds.com/ru/java/java-core-russian/interface-comparator

data class Person(val name: String, val age: Int)

fun main() {
val people = listOf(
Person("Alice", 25),
Person("Bob", 30),
Person("Charlie", 20)
)

val ageComparator = Comparator<Person> { p1, p2 -> p1.age - p2.age }
val sortedByAge = people.sortedWith(ageComparator)

println(sortedByAge)
}

➤ What is Dependency injection

a style of object customization in which the object’s fields are specified by an external entity. Dagger / Koin / Kodein is used for this
https://www.dataart.com.ua/news/dagger-2-lechim-zavisimosti-po-metodike-google/
https://developer.android.com/training/dependency-injection
https://habr.com/ru/post/350068/

➤ What are GET and POST requests

GET: getting data from the server

POST: sending data to the server

PUT: updating data on the server

DELETE: Deleting data from the server
Requests have headers, a body, and a response code (status code)
Examples of use with Retrofir:

@GET("https://www.google.com/{id}")
fun getSomeData(
@Path("id") id: String
): Single<Data>

@POST("https://www.google.com")
fun postSomeData(
@Body model: Model
): Single<Data>

// https://www.google.com/id=12345

@GET("https://www.google.com")
fun getSomeData(
@Query("id") id: String,
): Single<Data>

https://developer.mozilla.org/ru/docs/Web/HTTP/Messages
https://ru.wikipedia.org/wiki/HTTP
https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP

➤ What is Content Provider

Content Provider: It is a component of the application architecture in Android that provides access to application data from outside. It can be used to exchange data between applications, provide access to data from other applications, or to store and retrieve data from a database.
Here is an example of using Content Provider to save and retrieve data from an SQLite database in Android:
Defining the content provider in the application manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application>
...
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:exported="false" />
</application>
</manifest>

Create a MyContentProvider class that will inherit from ContentProvider and define the database and URI that will be used to access the data:

class MyContentProvider : ContentProvider() {

private lateinit var dbHelper: MyDatabaseHelper

override fun onCreate(): Boolean {
context?.let {
dbHelper = MyDatabaseHelper(it)
}
return true
}

override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper.readableDatabase
val cursor = db.query(
TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
cursor.setNotificationUri(context?.contentResolver, uri)
return cursor
}

override fun getType(uri: Uri): String? {
return "vnd.android.cursor.dir/$AUTHORITY.$TABLE_NAME"
}

override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper.writableDatabase
val id = db.insert(TABLE_NAME, null, values)
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}

override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbHelper.writableDatabase
val count = db.delete(TABLE_NAME, selection, selectionArgs)
context?.contentResolver?.notifyChange(uri, null)
return count
}

override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
val db = dbHelper.writableDatabase
val count = db.update(TABLE_NAME, values, selection, selectionArgs)
context?.contentResolver?.notifyChange(uri, null)
return count
}

companion object {
const val AUTHORITY = "com.example.myapp.provider"
const val TABLE_NAME = "my_table"
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/$TABLE_NAME")
}
}

Using a content provider to access data from another application:

val cursor = contentResolver.query(MyContentProvider.CONTENT_URI, null, null, null, null)

➤ What is Android Context

A base abstract class whose implementation is provided by the Android system. This class has methods for accessing application-specific resources and classes and is used to perform application-level operations such as launching activities, sending broadcast messages, receiving intents, and so on. Large and important classes such as Application, Activity and Service inherit from the Context class, so all its methods are accessible from these classes

Activity Context: created when an activity is created and destroyed along with the activity. Context is a heavy subject. When they talk about a memory leak in Android, they mean a context leak, i.e. a situation where the activity context is stored after calling Activity.onDestroy(). Do not pass the activity context to another object unless you know how long this object will live.

Application Context: singleton. The Application Context is created when the Application object is created and lives as long as the application process is alive. For this reason, Application Context can be safely injected into other singletons in the application. It is not recommended to use Application Context to start an activity, because it is necessary to create a new task, and for layout inflation, because the default theme is used.

You can get the context inside the code using one of the following methods:

getBaseContext: get a reference to the base context

getApplicationContext: get a reference to the application object

getContext: inside an activity or service, get a reference to this object)

this: same as getContext

MainActivity.this: inside a nested class or method, get a reference to the MainActivity object

getActivity: inside a fragment, get a reference to the parent activity object
There is also a difference

getContext(): Returns a nullable Context value.

requireContext(): Returns a non-null Context or throws an exception if it is not available.

If your code is in a phase of its lifecycle where you know your fragment is attached to a context, simply use requireContext() to get the Context and also to keep static analyzers happy with potential NPE issues.
If your code is outside the normal lifecycle of a fragment (say an async callback), you might be better off using getContext(), checking its return value yourself and only continuing to use it if it is non-null.

https://itsobes.ru/AndroidSobes/chem-otlichaetsia-activity-context-ot-application-context/
https://www.fandroid.info/context-kontekst-v-android-chto-eto-kak-poluchit-i-zachem-ispolzovat/

➤ What is the difference between map and flatMap

map: This is a function that takes a collection and a function that is applied to each element of the collection. This creates a new collection with the same number of elements as the original collection, but with changed values.

flatMap: This is a function that takes a collection and a function that returns a collection. flatMap then combines all the resulting collections into one collection.
The difference between map and flatMap is that map returns the collection of elements obtained after applying a function to each element of the original collection, while flatMap returns the collection of elements obtained after applying a function that returns a collection to each element of the original collection. Additionally, flatMap can be useful when you need to “unfold” nested collections into one larger collection.

➤ What is Serializable, Externalizable, Parcelable

Serializable: This is a token interface in Java that allows an object to be serialized (converted to a byte stream) so that it can be stored in a file, transmitted over the network, or used in other applications.
For a class to be serializable, it must implement the Serializable interface. After this, all fields of the class (except those marked with the transient keyword) will be serialized and can be restored to their original state by deserializing the byte stream.

val person = Person("John", 30)

// Serialize the object
val byteArrayOutputStream = ByteArrayOutputStream()
val objectOutputStream = ObjectOutputStream(byteArrayOutputStream)
objectOutputStream.writeObject(person)
objectOutputStream.close()

// Deserialize the object
val byteArrayInputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
val objectInputStream = ObjectInputStream(byteArrayInputStream)
val deserializedPerson = objectInputStream.readObject() as Person
objectInputStream.close()

println(deserializedPerson) // Output: Person(name=John, age=30)

Externalizable: An interface that extends the Serializable interface but provides more control over how objects are serialized and deserialized.
To use Externalizable, a class must implement this interface and override the writeExternal() and readExternal() methods, which are used to explicitly serialize and deserialize an object.
Example of using Externalizable:

class Person(var name: String, var age: Int) : Externalizable {
override fun readExternal(inStream: ObjectInput) {
name = inStream.readUTF()
age = inStream.readInt()
}

override fun writeExternal(outStream: ObjectOutput) {
outStream.writeUTF(name)
outStream.writeInt(age)
}
}

val person = Person("John", 30)

val file = File("person.bin")

// Write the object to a file
ObjectOutputStream(FileOutputStream(file)).use { it.writeObject(person) }

// Read an object from a file
ObjectInputStream(FileInputStream(file)).use { input ->
val newPerson = input.readObject() as Person
println(newPerson.name) // Prints "John"
println(newPerson.age) // Prints "30"
}

Parcelable: An object serialization mechanism in Android that allows you to pass objects between different application components via Intent, and save and restore the state of objects in a Bundle.
When using Parcelable, an object must implement the Parcelable interface, which contains methods for serializing and deserializing the object. Serialization breaks an object into individual chunks that can be passed between processes, resulting in better performance than Serializable.
Parcelable is often used in Android to transfer data between Activity, Fragment and Service.

// creating a Person object
val person = Person("Alice", 30)

// packing the Person object into an Intent
val intent = Intent(this, SecondActivity::class.java).apply {
putExtra("person", person)
}

// getting the Person object from the Intent
val personFromIntent = intent.getParcelableExtra<Person>("person")

The gson library also has the @SerializedName annotation, which allows you to set a name different from the variable name
A variable with the transient modifier is not serializable and should not be used to override equals() and hashCode()
Static variables are not serialized because they do not belong to an instance of the class

https://programmera.ru/uroki-po-java/chto-takoe-externalizable-v-Java/
https://metanit.com/java/tutorial/6.10.php
https://metanit.com/java/android/2.13.php

➤ What is WorkManager

WorkManager: An Android library for scheduling and running asynchronous tasks in the background. It provides an easy way to complete tasks even when the application is already closed, and also automatically manages the launch of tasks to minimize device resource consumption.

class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
// An asynchronous task is performed here, such as downloading a file
return Result.success()
}
}

val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context)
.enqueue(myWorkRequest)

// example for several

val workA = OneTimeWorkRequestBuilder<MyWorkerA>().build()
val workB = OneTimeWorkRequestBuilder<MyWorkerB>().build()
val workC = OneTimeWorkRequestBuilder<MyWorkerC>().build()
val workD = OneTimeWorkRequestBuilder<MyWorkerD>().build()

WorkManager.getInstance(context)
.beginWith(workA, workB, workC)
.combine()
.then(workD)
.enqueue()

enqueue(): Enqueues the task for execution. It will be performed as soon as the device is ready for this (for example, if there is an Internet connection, sufficient battery charge, etc.).

beginWith(): Used to start multiple tasks in a given order.

combine(): Used to run multiple tasks in parallel and wait for them to complete before executing the next task.

WorkManager and LiveData: Allows you to receive notifications about the status of tasks in WorkManager. For example, you can create a LiveData object and use it to display the progress of a task.

class MyViewModel(application: Application) : AndroidViewModel(application) {

private val workManager = WorkManager.getInstance(application)

private val workInfoLiveData = MutableLiveData<WorkInfo>()

fun startWork() {
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.build()

workManager.enqueue(workRequest)

workManager.getWorkInfoByIdLiveData(workRequest.id)
.observeForever { workInfo ->
workInfo?.let {
workInfoLiveData.postValue(workInfo)
}
}
}

fun getWorkInfoLiveData(): LiveData<WorkInfo> {
return workInfoLiveData
}
}

class MyActivity : AppCompatActivity() {

private lateinit var viewModel: MyViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

viewModel.getWorkInfoLiveData().observe(this, { workInfo ->
if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
// Display progress
} else if (workInfo != null && workInfo.state.isFinished) {
// Task completed
}
})

viewModel.startWork()
}
}

WorkManager and RxJava: You can use the RxWorkManager library, which provides a convenient way to create Observable streams from WorkRequest objects. Here’s an example of using RxWorkManager to run a OneTimeWorkRequest and get the result as a Single:

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.build()

val workObservable = RxWorkManager
.getInstance(application)
.getWorkInfoByIdObservable(workRequest.id)
.flatMap { workInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
val outputData = workInfo.outputData
Single.just(outputData.getString("result"))
} else {
Single.error(Throwable("Work failed"))
}
}

workObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
// Handle a successful result
},
{ error ->
// Error handling
}
)

WorkManager.getInstance(application).enqueue(workRequest)

WorkManager and Coroutines: You can use the kotlinx-coroutines-play-services library, which provides a convenient way to run and track tasks in WorkManager using coroutines.

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.build()

val workInfoDeferred = WorkManager.getInstance(context)
.getWorkInfoById(workRequest.id)
.asDeferred()

// Run the task in a background thread
GlobalScope.launch(Dispatchers.IO) {
WorkManager.getInstance(context).enqueue(workRequest)
}

// Wait for the result of the task in the main thread
val workInfo = workInfoDeferred.await()

if (workInfo.state == WorkInfo.State.SUCCEEDED) {
val outputData = workInfo.outputData
val result = outputData.getString("result")
// Handle a successful result
} else {
// Error handling
}

➤ Do Android programs run on the JVM?

No, the JVM bytecode is interpreted into ART (Android Runtime) bytecode and then executed. Before version 5.0, instead of ART there was DRT (Dalvik)

➤ What types of services are there in Android?

Foreground Service: A service that runs in the foreground and provides noticeable functionality to the user. A background service can be turned into a foreground service by calling the startForeground() method.

Background Service: A service that runs in the background and does not provide noticeable functionality to the user. Unlike the foreground service, the background service can be destroyed by the system at any time when the system needs to free up memory.

Bound Service: A service that provides communication between an application and another part of the system, such as another application or service. The Bound service can interact with the client by providing it with an IBinder object via the onBind() method. Clients can use this object to call methods that are defined in the service. When all clients disconnect from the service, it is destroyed.

IntentService: This is a subclass of Service that is used to perform tasks in the background.
IntentService automatically creates a new thread to handle each intent sent to the service. When the intent is processed, the service is automatically terminated. If a new intent arrives while the service is running, it is added to the queue and processed one by one.
The IntentService also provides synchronization by blocking new intents until the previous ones have completed processing.
The main use of IntentService is to perform long-running operations such as downloading data from the network, processing images, saving data to a database, etc. All of these operations can be done in the background so as not to block the main UI thread.

JobIntentService: This is a deprecated class in Android that is designed to perform background tasks that can be completed in the background using a service. It was introduced into the Android Support Library for backward compatibility. JobIntentService works similar to IntentService but has additional functionality to handle tasks that need to be completed in the background and to process those tasks in the background even after the application has been stopped. Unlike a regular IntentService, JobIntentService automatically manages the service lifecycle and ensures that the service is not stopped until all tasks are completed.
Starting with Android 12, it is recommended to use WorkManager to run background tasks instead of JobIntentService.

Example of using the service: Create a class for the service that inherits from Service. For example, create a MyService.kt file with the following content:

class MyService : Service() {

private val binder = MyServiceBinder()

override fun onCreate() {
// Executed when the service is created
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Executed when the service starts
return START_STICKY
}

override fun onDestroy() {
// Executed when the service ends
}

override fun onBind(intent: Intent?): IBinder? {
// method that should be overridden in the Service class,
// to provide a client (usually an activity) with the ability to contact the service through the IBinder interface.
// This method should return an IBinder object,
// which the client can use to interact with the service.
// If the service does not provide the ability to communicate with the client,
// this method can simply return null.
return binder
}

inner class MyServiceBinder : Binder() {
fun getService(): MyService {
return this@MyService
}
}
}

Add the service to the application manifest file, specifying the service class name and permissions if necessary. For example, add the following code to the AndroidManifest.xml file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">

<service
android:name=".MyService"
android:enabled="true"
android:exported="false" />

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

Start and stop:

val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)

val serviceIntent = Intent(this, MyService::class.java)
stopService(serviceIntent)

https://javadevblog.com/service-i-intentservice-v-android-rukovodstvo-po-sluzhbam-i-primer-ispol-zovaniya.html

➤ What is Android ViewBinding

Android ViewBinding: This is a mechanism for automatically generating binding classes for layouts in Android. It allows views in a layout to be associated with corresponding View objects in application code. This simplifies the process of accessing views in code and eliminates the need to use findViewById() methods to find elements in the layout.
To use Android View Binding in your app, you need to enable it in your Gradle file and enable the binding feature in your markup. The link generator will then automatically create a link class for each layout that you can use to reference views in your application code.

➤ What is onTrimMemory()

Called when the operating system determines that it is time for a process to remove unnecessary memory. This will happen, for example, when it is running in the background and there is not enough memory to support many background processes.

➤ What is Java ThreadPool

Limited list of threads that can be reused, example:

val threadPool = Executors.newFixedThreadPool(4)
// create a thread pool with a fixed number of threads (4)
for (i in 1..10) {
threadPool.submit { // add a task to be executed to the pool
println("Task $i executed by thread ${Thread.currentThread().name}")
}
}
threadPool.shutdown() // close the thread pool

➤ What are the Scope Functions in Kotlin?

Kotlin has five Scope Functions that allow you to change the scope of variables, and also make your code easier to read and reduce the likelihood of errors:

let: allows you to execute a block of code on the object passed as an argument and return the result of that block. Inside a block, you can use a reference to an object via it.

val result = someObject?.let { it.property } ?: defaultValue

run: Executes a block of code on the object passed as this and returns the result of that block. Inside a block, you can use a reference to an object via this.

val result = someObject?.run { property } ?: defaultValue

with: Executes a block of code that passes an object as an argument, and returns the result of that block.

val result = with(someObject) { property } ?: defaultValue

apply: Executes a block of code on the object passed as this and returns that object. Inside a block, you can use a reference to an object via this.

val someObject = SomeClass().apply {
property1 = value1
property2 = value2
}

also: allows you to execute a block of code on the object passed as an argument and return that object. Inside a block, you can use a reference to an object via it.

val someObject = SomeClass().also {
it.property1 = value1
it.property2 = value2
}

they can be combined

variable?.let { variable not null } ?: run { variable null }

// same as

if(variable != null) { variable not null } else { variable null }

https://bimlibik.github.io/posts/kotlin-scope-functions/
https://kotlinlang.ru/docs/reference/scope-functions.html

➤ What is sharedPreferences, how does it work and where does it save it?

SharedPreferences: Saves data as key-value pairs to application folders. It has an Editor for editing, in which commit(), which returns the results of saving, and apply(), which does not return results.
SharedPreferences has an OnSharedPreferenceChangeListener which allows you to listen for changes by key
https://developer.android.com/reference/android/content/SharedPreferences

EncryptedSharedPreferences: This is an encrypted implementation of sharedPreferences
https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences

DataStore: This is a replacement for SharedPreferences that addresses most of these shortcomings. DataStore includes a fully asynchronous API using Kotlin Coroutines and Flow.
https://developer.android.com/topic/libraries/architecture/datastore

➤ How can you transfer data between activities and between fragments

Data between activities is transferred through Intent for putExtra and Bundle for getExtras. Recently, a type-safe method of passing through SafeArgs has also appeared.
Between fragments, data is usually transferred through an interface.
To add primitives to a Bundle, use

Fragment().apply {
arguments = Bundle().apply {
putBoolean(key, value),
putString(key, value)
}
}

used for classes

getParcelable(key, class instance)

used to receive

val value = arguments?.getBoolean(key) and
val value = arguments?.getParcelable<class type>(key)

➤ How to encrypt/decrypt data using AES/RSA algorithm or get SHA-1/MD5 hash

void getHashWithSHA(String text) throws Exception {
MessageDigest messageDigestSHA = MessageDigest.getInstance("SHA-1");
byte[] sha = messageDigestSHA.digest(text.getBytes());
}

void getHashWithMD5(String text) throws Exception {
MessageDigest messageDigestMD5 = MessageDigest.getInstance("MD5");
byte[] md5 = messageDigestMD5.digest(text.getBytes());
}

void encryptWithRSA(String text) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();
Key privateKey = keyPair.getPrivate();
Key publicKey = keyPair.getPublic();
// or the second option for obtaining the key
SecureRandom secureRandom = new SecureRandom();
keyPairGenerator.initialize(512, secureRandom);
//
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(text.getBytes());
}

void decryptWithRSA(byte[] encrypted, Key privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(encrypted);
String text = new String(decrypted, "UTF8");
}

void encryptWithAES(String text) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey key1 = keyGenerator.generateKey();
// or the second option for obtaining the key
SecretKeySpec key2 = new SecretKeySpec("qwerty".getBytes(), "AES");
//
cipher.init(Cipher.ENCRYPT_MODE, key1);
// there is also Cipher.UNWRAP_MODE and Cipher.WRAP_MODE - this is encryption on top
byte[] encrypted = cipher.doFinal(text.getBytes());
}

void decryptWithAES(byte[] encrypted, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encrypted);
String text = new String(decrypted, "UTF8");
}

➤ How LiveData differs from a regular RxJava subscription

LiveData is tied to lifecycle, RxJava is not tied

➤ What is the difference between MVP, MVVM, MVC, MVI

MVC (Model-View-Controller): Divides the application into three components: Model, View and Controller. In this architecture, the Model contains the data and business logic of the application, the View displays the data to the user, and the Controller processes user input and manages changes to the data.

// Model

data class User(val id: Int, val name: String, val email: String)

// View

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/tv_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<TextView
android:id="@+id/tv_user_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>

class MainActivity : AppCompatActivity(), ViewContract {
private lateinit var tvUserName: TextView
private lateinit var tvUserEmail: TextView

private val presenter: Presenter = Presenter(this)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

tvUserName = findViewById(R.id.tv_user_name)
tvUserEmail = findViewById(R.id.tv_user_email)

presenter.loadUser()
}

override fun showUser(user: User) {
tvUserName.text = user.name
tvUserEmail.text = user.email
}
}

// Controller

class Presenter(private val view: ViewContract) {
private val repository = UserRepository()

fun loadUser() {
val user = repository.getUser()
view.showUser(user)
}
}

MVP (Model-View-Presenter): divides the application into three components: Model, View and Presenter. In this architecture, the Model contains the data and business logic of the application, the View displays the data to the user, and the Representative manages the interaction between the Model and View.

interface LoginView {
fun showProgress()
fun hideProgress()
fun setUsernameError()
fun setPasswordError()
fun navigateToHome()
}

interface LoginPresenter {
fun validateCredentials(username: String, password: String)
fun onDestroy()
}

class LoginPresenterImpl(private var view: LoginView?) : LoginPresenter {
override fun validateCredentials(username: String, password: String) {
if (username.isEmpty()) {
view?.setUsernameError()
return
}
if (password.isEmpty()) {
view?.setPasswordError()
return
}
// Here we would check the user's credentials in the API or DB.
view?.showProgress()
Handler().postDelayed({
view?.hideProgress()
view?.navigateToHome()
}, 2000)
}
override fun onDestroy() {
view = null
}
}

class LoginActivity : AppCompatActivity(), LoginView {

private var presenter: LoginPresenter? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)

presenter = LoginPresenterImpl(this)

loginButton.setOnClickListener {
presenter?.validateCredentials(usernameEditText.text.toString(), passwordEditText.text.toString())
}
}

override fun showProgress() {
progressBar.visibility = View.VISIBLE
}

override fun hideProgress() {
progressBar.visibility = View.GONE
}

override fun setUsernameError() {
usernameEditText.error = getString(R.string.username_error)
}

override fun setPasswordError() {
passwordEditText.error = getString(R.string.password_error)
}

override fun navigateToHome() {
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)
finish()
}

override fun onDestroy() {
super.onDestroy()
presenter?.onDestroy()
presenter = null
}
}

MVVM (Model-View-ViewModel): divides the application into three components: Model (Model), View (View) and Model Representative (ViewModel). In this architecture, the Model contains the data and business logic of the application, the View displays the data to the user, and the Model Representative is responsible for the communication between the Model and the View. ViewModel cannot directly influence View, for example LiveData is used, where View subscribes to data updates in ViewModel

// Model

data class User(val name: String, val age: Int)

//ViewModel

class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user

fun loadUser() {
// Retrieving user data from the repository
val userRepository = UserRepository()
val user = userRepository.getUser()

// Update _user value
_user.value = user
}
}

// View

class UserActivity : AppCompatActivity() {
private val userViewModel: UserViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)

userViewModel.user.observe(this, { user ->
// Update the UI with new user data
nameTextView.text = user.name
ageTextView.text = user.age.toString()
})

// Load user data
userViewModel.loadUser()
}
}

MVI (Model-View-Intent): Divides the application into three components: Model, View and Intent. In this architecture, the Model contains the data and business logic of the application, the View displays data to the user, and the Intent describes the user’s intent to change the state of the View.

// Model

data class Counter(val value: Int)

// View

class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<CounterViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

incrementButton.setOnClickListener { viewModel.increment() }
decrementButton.setOnClickListener { viewModel.decrement() }

viewModel.counter.observe(this, Observer { counter ->
counterTextView.text = counter.value.toString()
})
}
}

// Intent

sealed class CounterIntent {
object Increment : CounterIntent()
object Decrement : CounterIntent()
}

// ViewModel

class CounterViewModel : ViewModel() {
private val _counter = MutableLiveData(Counter(0))
val counter: LiveData<Counter> = _counter

fun increment() {
val currentValue = _counter.value ?: return
_counter.value = currentValue.copy(value = currentValue.value + 1)
}

fun decrement() {
val currentValue = _counter.value ?: return
_counter.value = currentValue.copy(value = currentValue.value - 1)
}

fun processIntent(intent: CounterIntent) {
when (intent) {
is CounterIntent.Increment -> increment()
is CounterIntent.Decrement -> decrement()
}
}
}

// State

data class CounterState(val counter: Counter)

// Reducer

fun reduce(state: CounterState, intent: CounterIntent): CounterState {
return when (intent) {
is CounterIntent.Increment -> state.copy(counter = state.counter.copy(value = state.counter.value + 1))
is CounterIntent.Decrement -> state.copy(counter = state.counter.copy(value = state.counter.value - 1))
}
}

// Observer

class CounterObserver(private val viewModel: CounterViewModel) : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
viewModel.counter.observe(owner, Observer { counter ->
val state = CounterState(counter)
render(state)
})
}

private fun render(state: CounterState) {
// do something with the state
}
}

Comparison of MVP and MVVM:

MVP Cons:
Circular dependency between View and Presenter
In MVP, the View stores a reference to the Presenter, and the Presenter references the View. It’s not so scary, because… Presenter doesn’t know about the concrete class that implements the View interface, but the fact remains.
Because the Presenter refers to a View, its lifetime must not exceed the lifetime of the View. The Presenter must stop all asynchronous operations before the Activity or Fragment enters the destroyed state.
This brings us to the second problem.
Presenter provides methods to handle the View lifecycle
In a good MVP Presenter implementation, there are no lifecycle methods such as onStart()/onStop(). Instead, methods like init() are created to load and display data, and unsubscribe() to unsubscribe from asynchronous calls.
With this implementation, the Presenter is unaware of the lifecycle, but the View must take care of calling these methods itself. If the View does not call unsubscribe() before the onDestroy() method is executed, this may lead to an Activity leak.

Pros of MVVM:
In MVVM, VIew no longer has an interface, as it simply subscribes to observable fields in the ViewModel.
ViewModel is easier to reuse since it doesn’t know anything about View. (follows from the first point)
MVVM solves the state problem, but not completely. In this example, we have a property in the ViewModel, from which the View takes data. When we make a request to the network, the data will be saved in property, and the View will receive valid data when subscribing (and you don’t even need to dance with a tambourine). We can also make properties persistent, which will allow them to be saved in case of process death.

➤ What are the most commonly used patterns

Singleton: Ensures that a class has only one instance and provides a global access point to it.

public class Singleton {
private static Singleton instance = null;
private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

object Singleton {
init {
println("Singleton instance has been created.")
}

fun doSomething() {
println("Singleton is doing something.")
}
}

Observer: Establishes a dependency between objects so that if one object changes, all its dependent objects are notified and automatically updated.

Builder: Allows you to create objects using a step-by-step process in which you can set various parameters, and ultimately end up with an object with certain properties.

class Person private constructor(
val firstName: String?,
val lastName: String?,
) {

class Builder {
private var firstName: String? = null
private var lastName: String? = null

fun firstName(firstName: String) = apply { this.firstName = firstName }
fun lastName(lastName: String) = apply { this.lastName = lastName }
fun build() = Person(firstName, lastName, age, city, country)
}
}

val person = Person.Builder()
.firstName("John")
.lastName("Doe")
.build()

Factory: Provides a common interface for creating objects, but delegates the actual creation of objects to subclasses.

interface Transport {
fun deliver(): String
}

class Car : Transport {
override fun deliver() = "Delivering by car"
}

class Truck : Transport {
override fun deliver() = "Delivering by truck"
}

enum class TransportType {
CAR,
TRUCK,
}

object TransportFactory {
fun createTransport(transportType: TransportType): Transport {
return when (transportType) {
TransportType.CAR -> Car()
TransportType.TRUCK -> Truck()
}
}
}

val car = TransportFactory.createTransport(TransportType.CAR)
println(car.deliver()) // "Delivering by car"

val truck = TransportFactory.createTransport(TransportType.TRUCK)
println(truck.deliver()) // "Delivering by truck"

Adapter: Converts the interface of one class into the interface expected by another class so that they can interact with each other.

interface MediaPlayer {
fun play(audioType: String, fileName: String)
}

interface AdvancedMediaPlayer {
fun playVlc(fileName: String)
fun playMp4(fileName: String)
}

class VlcPlayer : AdvancedMediaPlayer {
override fun playVlc(fileName: String) {
println("Playing vlc file. Name: $fileName")
}

override fun playMp4(fileName: String) {
// do nothing
}
}

class Mp4Player : AdvancedMediaPlayer {
override fun playVlc(fileName: String) {
// do nothing
}

override fun playMp4(fileName: String) {
println("Playing mp4 file. Name: $fileName")
}
}

class MediaAdapter(audioType: String) : MediaPlayer {

private val advancedMediaPlayer: AdvancedMediaPlayer?

init {
when (audioType) {
"vlc" -> advancedMediaPlayer = VlcPlayer()
"mp4" -> advancedMediaPlayer = Mp4Player()
else -> advancedMediaPlayer = null
}
}

override fun play(audioType: String, fileName: String) {
when (audioType) {
"vlc" -> advancedMediaPlayer?.playVlc(fileName)
"mp4" -> advancedMediaPlayer?.playMp4(fileName)
else -> println("Invalid media. $audioType format not supported")
}
}
}

class AudioPlayer : MediaPlayer {
private val mediaAdapter: MediaAdapter?

override fun play(audioType: String, fileName: String) {
when (audioType) {
"mp3" -> println("Playing mp3 file. Name: $fileName")
"vlc", "mp4" -> {
mediaAdapter = MediaAdapter(audioType)
mediaAdapter.play(audioType, fileName)
}
else -> println("Invalid media. $audioType format not supported")
}
}
}

Decorator: Dynamically adds new functionality to objects by wrapping them in other objects that have that functionality.

abstract class Beverage {
abstract val description: String
abstract fun cost(): Double
}

abstract class CondimentDecorator : Beverage()

class Milk(private val beverage: Beverage) : CondimentDecorator() {
override val description = "${beverage.description}, Milk"
override fun cost() = beverage.cost() + 0.1
}

val espresso = Espresso()
val latte = Milk(Whip(Mocha(espresso)))

Facade: Provides a unified interface to a group of interfaces in a subsystem, thereby simplifying interaction with it.

class OrderProcessor {
fun processOrder(order: Order): String {
val warehouse = Warehouse()
val paymentSystem = PaymentSystem()

val warehouseResult = warehouse.checkInventory(order)
if (warehouseResult == "available") {
val paymentResult = paymentSystem.processPayment(order)
if (paymentResult == "success") {
warehouse.updateInventory(order)
return "Order processed successfully"
}
}
return "Order processing failed"
}
}

class Warehouse {
fun checkInventory(order: Order): String {
// check inventory
return "available"
}

fun updateInventory(order: Order) {
// update inventory
}
}

class PaymentSystem {
fun processPayment(order: Order): String {
// process payment
return "success"
}
}

class Order {
// order details
}

class OrderFacade {
fun processOrder(order: Order): String {
val warehouseResult = checkInventory(order)
if (warehouseResult == "available") {
val paymentResult = processPayment(order)
if (paymentResult == "success") {
updateInventory(order)
return "Order processed successfully"
}
}
return "Order processing failed"
}

private fun checkInventory(order: Order): String {
val warehouse = Warehouse()
return warehouse.checkInventory(order)
}

private fun updateInventory(order: Order) {
val warehouse = Warehouse()
warehouse.updateInventory(order)
}

private fun processPayment(order: Order): String {
val paymentSystem = PaymentSystem()
return paymentSystem.processPayment(order)
}
}

Template Method: Defines the basis of an algorithm, but allows subclasses to override some of the steps of that algorithm without changing its overall structure.

bstract class Pizza {

fun make() {
prepareDough()
addIngredients()
bakePizza()
cutPizza()
}

protected fun prepareDough() {
println("Preparing pizza dough")
}

protected abstract fun addIngredients()

protected fun bakePizza() {
println("Baking pizza")
}

protected fun cutPizza() {
println("Cutting pizza")
}
}

class PepperoniPizza : Pizza() {

override fun addIngredients() {
println("Adding pepperoni to pizza")
}
}

class MargheritaPizza : Pizza() {

override fun addIngredients() {
println("Adding mozzarella and basil to pizza")
}
}

fun main() {
val pepperoniPizza = PepperoniPizza()
pepperoniPizza.make()

val margheritaPizza = MargheritaPizza()
margheritaPizza.make()
}

// Preparing pizza dough
// Adding pepperoni to pizza
// Baking pizza
// Cutting pizza
// Preparing pizza dough
// Adding mozzarella and basil to pizza
// Baking pizza
// Cutting pizza

Strategy: When using the strategy pattern, we move some logic out of the main code into separate classes that implement a common interface. In the main code, we can then select the desired implementation depending on certain conditions, without having to use a lot of conditional statements.

interface PaymentStrategy {
fun pay(amount: Double)
}

class CreditCardStrategy(private val cardNumber: String, private val cvv: String) : PaymentStrategy {
override fun pay(amount: Double) {
// logic for paying with a credit card
}
}

class PayPalStrategy(private val email: String, private val password: String) : PaymentStrategy {
override fun pay(amount: Double) {
// payment logic using PayPal
}
}

class PaymentProcessor(private val paymentStrategy: PaymentStrategy) {
fun processPayment(amount: Double) {
paymentStrategy.pay(amount)
}
}

// usage
val paymentStrategy = if (useCreditCard) {
CreditCardStrategy(cardNumber, cvv)
} else {
PayPalStrategy(email, password)
}
val paymentProcessor = PaymentProcessor(paymentStrategy)
paymentProcessor.processPayment(amount)

➤ How RecyclerView works

RecyclerView: This is a UI widget in Android that is used to display a large set of data in a scrollable manner on the screen. It allows you to efficiently recycle list item views while the user scrolls through the list.
When the RecyclerView is displayed on the screen, it begins to create list items and associate them with data. RecyclerView uses the ViewHolder class to cache list item views to avoid having to create new views for each item when it becomes visible. RecyclerView also uses LayoutManager to determine how list items are laid out on the screen, such as a grid or a vertical list.
RecyclerView uses an Adapter to bind data to list item views. The adapter defines how to create views of list items and how to bind them to data. The adapter also allows RecyclerView to know the number of items in the list and which items are currently visible on the screen.
When you scroll a list, RecyclerView reuses views of list items that are no longer visible on the screen to create new views for items that appear when you scroll. This can significantly reduce memory usage and increase performance when working with large data sets.

➤ What is the difference between the launch and async method of launching coroutines?

There are two functions for launching a coroutine: launch{} and async{}.

launch{}: returns nothing,

fun main(args: Array<String>) {
print("1 ")
val job: Job = GlobalScope.launch {
print("3")
delay(1000L)
print("4")
}
print("2")
// 1 2 but will not be executed further since the program will end earlier
// or you can do job.cancel()
}

async{}: Returns a Deferred instance that has an await() function that returns the result of the coroutine, just like Future in Java where we do future.get() to get the result.

suspend fun main(args: Array<String>) {
print("1 ")
val deferred: Deferred<String> = GlobalScope.async {
return@async "3 "
}
print("2 ")
print(deferred.await())
print("4 ")
// 1 2 3 4
}

➤ What is the difference between relational and non-relational databases

Relational databases (RDBMS) and non-relational databases (NoSQL) differ primarily in the structure of the data and the way it is organized.
Relational databases store data in the form of tables that contain rows and columns. All rows in the table must follow a certain structure that is specified in advance. In relational databases, data is stored in relationships (links) between tables.
Non-relational databases store data in different formats such as documents, key-value, columns and graphs. These databases can store unstructured data and do not require a strict data schema.
Relational databases are better suited for working with data that has a fixed structure, changes frequently, and requires complex queries to process the data. Non-relational databases are more suitable for processing data with an uncertain structure and low coupling between data.
Other major differences between relational and non-relational databases include how data is stored, indexing, horizontal and vertical scaling capabilities, query processing methods, and hardware requirements.

➤ What is the difference between GlobalScope / CoroutineScope / LifecycleScope / ViewModelScope

GlobalScope: This is a scope that lives throughout the application’s lifecycle. Coroutines running in this area will live and run until the application is stopped or an unmanaged error occurs.

CoroutineScope: This is a scope that is associated with a specific application component, such as an activity or fragment. In this scope, coroutines running in this scope will be canceled automatically when the component to which this scope is associated is destroyed.

LifecycleScope: This is a scope that is associated with the lifecycle of a component. For this scope, a special LifecycleCoroutineScope object is created, which is responsible for automatically canceling coroutines when the component is destroyed.

ViewModelScope: This is a scope that is associated with the lifecycle of the ViewModel. For this scope, a special ViewModelCoroutineScope object is created, which is responsible for automatically canceling coroutines when the ViewModel is destroyed.

Using these scopes helps you avoid memory leaks, cancel coroutines at the right time, and manage the lifecycle of coroutines depending on the lifecycle of your component or application.

➤ What is the difference between Custom View and Custom Drawing|

Custom View: This is a custom widget that is created by extending an existing View class or its subclass. When creating a custom view, we can define new attributes, methods and event handlers, and change the view’s display. Custom View can be reused across multiple layouts and gives you the ability to create your own UI components.

Custom Drawing: This is the process of drawing on a canvas using the methods of the Canvas class. This approach allows you to draw various elements on the screen, such as lines, rectangles, circles, text, images, etc. Custom Drawing can be used when there is a need to create dynamic UI elements that cannot be achieved using standard UI elements.

➤ How does coroutine work and what is suspend?

Regular function: have no state and always run from scratch (unless, of course, they use global variables)
must complete its execution before returning control to its caller

Coroutine: has a state and can pause and resume its execution at certain points, thus returning control before completing its execution.
A coroutine is not tied to a native thread, it can suspend execution in one thread and resume execution in another, coroutines do not have their own stack, do not require a processor context switch, so they work faster.

There are two functions for launching a coroutine: launch{} and async{}.
launch{}: returns nothing
async{}: returns a Deferred instance that has an await() function that returns the result of the coroutine
CoroutineScope: Monitors any coroutine created using launch or async (these are extension functions in CoroutineScope). Current work (running coroutines) can be canceled by calling scope.cancel() at any time.
Job: element that controls the coroutine. For each coroutine created (via launch or async), it returns a Job instance that uniquely identifies the coroutine and manages its lifecycle. A Job can go through many states: new, active, completed, completed, cancelled, and canceled. Although we don’t have access to the states themselves, we can access the Job properties: isActive, isCancelled, and isCompleted.
CoroutineContext: This is a set of elements that define the behavior of a coroutine.
It consists of:
Job: Manages the life cycle of a coroutine.
CoroutineDispatcher: Dispatches work to the appropriate thread.
CoroutineName: The name of the coroutine, useful for debugging.
CoroutineExceptionHandler: Handles uncaught exceptions, which will be covered in part 3 of the coroutine series.
Coroutines have delay(1000L) — non-blocking delay

suspend fun doSomeWork() = coroutineScope {
launch { doWork() }
}
suspend fun doWork() {
println("before")
delay(400L)
println("after")
}

Since this function uses the delay() function, doWork() is defined with the suspend modifier. The coroutine itself is also created using the launch() function, which calls the doWork() function.
When a delay is called using the delay() function, the coroutine frees the thread it was running on and is stored in memory. And the freed thread can be used for other tasks. And when the running task is completed (for example, executing the delay() function), the coroutine resumes its work in one of the free threads.

https://kotlinlang.ru/docs/reference/coroutines-basics.html
https://medium.com/nuances-of-programming/%D0%BA%D0%BE%D1%80%D1%83%D1%82%D0%B8%D0%BD%D1%8B-%D1% 81%D0%B0%D0%BC%D0%BE%D0%B5-%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%BE%D0%B5-d421bb4523d9
https://tech-geek.ru/how-coroutines-work-kotlin/
https://youtu.be/yBxaE-qAPdI
https://manuelvivo.dev/suspend-modifier

➤ What is Deep Link and App Link

Deep Link: This is a link to specific content within a mobile application. This allows users to go directly to the desired app screen without having to go through additional steps such as searching within the app. An app that supports Deep Link can process specific URLs and launch corresponding app screens if they are available on the user’s device.

App Link: This is a more advanced version of Deep Link that allows an app to register to treat certain URLs as its own. This means that the user can navigate to the app’s content through the website and the system will automatically open the corresponding app if it is installed on the user’s device. The app can also provide information about itself to the website, such as icons and titles, making the experience more intuitive and engaging for users.

➤ What is Navigation Architecture Component, what is it for and how does it work

NavGraph: a list of fragments that we will create and populate. NavController will only be able to show fragments from this list

NavHostFragment: container. Inside it, the NavController will display fragments.

NavController: An object that controls the navigation of the application in NavHost. It coordinates the change of destination content in NavHost as the user navigates through the application.

<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_main"
app:startDestination="@id/mainFragment">

<fragment
android:id="@+id/mainFragment"
android:name="com._.Main"
android:label="mainFragment"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_settingsFragment"
app:destination="@id/settingsFragment"
app:popUpTo="@id/settingsFragment"
app:popUpToInclusive="true"/>
<action
android:id="@+id/action_mainFragment_to_settingsBottomSheetFragment"
app:destination="@id/settingsBottomSheetFragment" />
</fragment>

<fragment
android:id="@+id/settingsFragment"
android:name="com._.settingsFragment"
android:label="settingsFragment"
tools:layout="@layout/fragment_settings">
</fragment>

<dialog
android:id="@+id/settingsBottomSheetFragment"
android:name="com._.SettingsBottomSheetFragment"
android:label="settingsBottomSheetFragment"
tools:layout="@layout/bottom_sheet_settings" />

</navigation>

➤ What is Content Uri

Content Uri (or Content URI): This is the URI (Uniform Resource Identifier) for accessing data inside Android applications. It is used to access various types of data such as images, videos, audio, contacts, calendars, etc.
Content Uri are typically in the format “content://authority/path/id”, where “authority” is the identifier of the content provider, “path” is the path to a specific resource, and “id” is the unique identifier of the resource.
For example, to access photos stored in the device’s gallery, you can use the following Content Uri: “content://media/external/images/media”. To access a specific image, you can add the image ID at the end of the Uri: “content://media/external/images/media/123”.
Content Uri is used to exchange data between various Android applications and components, and to perform data read and write operations such as adding, updating, and deleting entries.

➤ How to make transition animations for a navigation component

In the res folder, an anim folder is created with animation resources
In the res/navigation/nav_graph.xml file, on the design tab, select the transition arrow between screens and open the Animations section in the attributes panel on the right.
A transition has four properties that can be animated:

enterAnim: enter the destination

exitAnim: exit from destination

popEnterAnim: Enter destination using pop action

popExitAnim: Exit destination using pop action
View can be animated using Transition Framework

➤ What are the advantages and disadvantages of png and svg and when is it better to use one or the other?

PNG is a raster format, with high magnification pixels will be visible, SVG is vector and does not depend on screen resolution

➤ Tell us about SOLID

Principles collected by Robert Martin, author of Clean Architecture, from which I will quote the answers

Single Responsibility Principle: the principle of single responsibility. A class should only have one reason to change. A bad implementation because it has many reasons to change.

class Man {
void work() {}
void eat() {}
void sleep() {}
}

Correct implementation in which each class has only one reason for change

class Man {
}

class Work extends Man {
void work() {}
}

class Eat extends Man {
void eat() {}
}

class Sleep extends Man {
void sleep() {}
}
// паттерн Фасад
class ManFacade {
Work work = new Work();
Eat eat = new Eat();
Sleep sleep = new Sleep();
void work() {
work.work();
}
void eat() {
eat.eat();
}
void sleep() {
sleep.sleep();
}
}

Open-Closed Principle: the principle of openness/closedness, software entities must be open for extension and closed for change
A bad implementation option, since the direction of the dependencies
Main -> Toyota

public class Main {
public static void main(String[] args) {
SportToyota sportToyota = new SportToyota();
workInTaxi(sportToyota);
}
static void workInTaxi(Toyota toyota){
if(toyota instanceof SportToyota){
((SportToyota) toyota).getOnePassenger();
} else {
toyota.getFourPassengers();
}
}
}
class Toyota {
void getFourPassengers(){ }
}
class SportToyota extends Toyota {
void getOnePassenger(){ }
}

The correct implementation option, since the direction of the dependencies
Main -> Car <- Toyota

public class Main {
public static void main(String[] args) {
SportToyota sportToyota = new SportToyota();
workInTaxi(sportToyota);
}
static void workInTaxi(Car car){
car.workInTaxi();
}
}
interface Car {
void workInTaxi();
}
class Toyota implements Car {
void getFourPassengers(){ }
@Override
public void workInTaxi() {
getFourPassengers();
}
}
class SportToyota extends Toyota {
void getOnePassenger(){ }
@Override
public void workInTaxi() {
getOnePassenger();
}
}

Liskov Substitution Principle: Barbara Liskov’s substitution principle. To create software systems from interchangeable parts, those parts must comply with a contract that allows those parts to be replaced with each other. When inheriting, we should not affect the functionality of parent classes
A bad implementation option, it changes the operation of the methods of the parent class

public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setHeight(10);
rectangle.setWeight(12);
rectangle.getSquare();
rectangle = new Square();
rectangle.setHeight(10);
rectangle.setWeight(12);
rectangle.getSquare();
// there will be an error here
}
}
class Rectangle {
protected int weight;
protected int height;
void setWeight(int weight) {
this.weight = weight;
}
void setHeight(int height) {
this.height = height;
}
int getSquare() {
return weight * height;
}
}
class Square extends Rectangle {
@Override
void setWeight(int weight) {
this.weight = weight;
this.height = weight;
}
@Override
void setHeight(int height) {
this.height = height;
this.weight = height;
}
@Override
int getSquare() {
return weight * height;
}
}

The correct implementation

public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setHeight(10);
rectangle.setWeight(12);
rectangle.getSquare();
Square square = new Square();
square.setSide(10);
square.getSquare();
}
}
interface Shape{
int getSquare();
}
class Rectangle implements Shape {
protected int weight;
protected int height;
void setWeight(int weight) {
this.weight = weight;
}
void setHeight(int height) {
this.height = height;
}

@Override
public int getSquare() {
return weight * height;
}
}
class Square implements Shape {
int side;
public void setSide(int side) {
this.side = side;
}
@Override
public int getSquare() {
return side * side;
}
}

Interface Segregation Principle: the principle of separating interfaces.
Avoid dependence on anything that is not used. There should be no situation where interface methods are not used in their implementation. A bad implementation option, it does not use the work method for Intern

interface Worker {
void work();
void eat();
}
class Man implements Worker {
@Override
public void work() {
System.out.println("work");
}
@Override
public void eat() {
System.out.println("eat");
}
}
class Intern implements Worker {
@Override
public void work() {
// intern is study, not work
}
@Override
public void eat() {
System.out.println("eat");
}
}

The correct implementation option, we divide the interface into several

interface Worker {
void work();
}
interface Eater {
void eat();
}
interface Person extends Worker, Eater {
}
class Man implements Person {
@Override
public void work() {
System.out.println("work");
}
@Override
public void eat() {
System.out.println("eat");
}
}
class Intern implements Eater {
@Override
public void eat() {
System.out.println("eat");
}
}

Dependency Inversion Principle: the principle of dependency inversion.
The code that implements the high-level policy should not depend on the code that implements the low-level details. Instead, the details should depend on politics.
All dependencies in the source code cross this boundary in one direction — towards abstraction.
The abstract component contains all the high-level business rules of the application.
A specific component contains the implementation details of these rules.

➤ What is REP / CCP / CRP

REP (Reuse/Release Equivalence Principle): An object-oriented design principle that states that an object created for use must be released in the same context in which it was created.
This means that an object created in a method or function must be released in that same method or function. If an object was created in a class, then it must be released in the same class. This helps prevent memory leaks caused by implicitly storing references to objects that are no longer used.
The principle of equivalence of reuse and release is related to the Single Responsibility Principle, which states that each object should have only one responsibility. If an object cannot be used in another context, then this may be a sign that it violates this principle.

CCP (Common Closure Principle): An object-oriented design principle that says classes that change together should be combined together.
This means that classes that perform similar functions or are related to each other should be placed in the same module or package. Thus, a change in one class will result in a change in other classes in the same module or package. This makes code changes easier and prevents unrelated changes in different parts of the application.
The principle of shared closure is related to the Single Responsibility Principle, which says that each class should have only one responsibility. If a class does too many things, it can make it difficult to change the code in the future, and this could be a sign that it violates the principle of general closure.

CRP (Common Reuse Principle): that classes should be designed in such a way that they can be reused in other projects and contexts without changing their code. This is achieved by separating common functions and properties into separate classes or components that can be reused in different contexts.
The principle of shared reuse reduces the amount of code reused in a project, speeds up the development process, provides greater flexibility, and reduces code maintenance costs.

➤ What are the connections between components?

Strong coupling: When two or more components are closely related and it is difficult to separate one component from the other. This can lead to problems with modularity and code reuse, and can make the application more difficult to test and maintain.

Weak coupling: when components depend less on each other and are more independent. This can make the application easier to test and maintain, as well as improve modularity and code reuse.

Functional coupling: When components are connected together only to perform a specific function or task. This can be useful for reducing application complexity and improving its performance.

Necessary coupling: When components must be connected to each other to perform a specific task. This may be necessary to ensure the application functions correctly, for example when transferring data between components.

Unnecessary coupling: When components are connected to each other even though this is not required to complete a task. This can increase the complexity of the application and make it more difficult to test and maintain.

Logical coupling: when components are connected to each other at a logical level, for example, when working with common data or functionality. This can be useful for simplifying code and making it more understandable.

➤ What are the difficulty levels for collections?

Constant time (O(1)): Operations are performed in constant time, independent of the size of the collection. Examples of operations with this complexity estimate: adding and removing elements from a HashSet, getting an element by index from an Array.

Logarithmic time (O(log n)): Operations are performed in a time that is logarithmic to the size of the collection. Examples of operations with this complexity rating: adding and removing elements from a TreeSet, searching for an element in a TreeMap.

Linear time (O(n)): operations are performed in a time linearly dependent on the size of the collection. Examples of operations with this complexity rating: searching for an element in an ArrayList, removing an element from a LinkedList.

Linearithmic time (O(n log n)): operations are performed in time linearly multiplied by the logarithm of the collection size. Examples of operations with this complexity rating: sorting elements in a List using the Merge Sort algorithm.

Quadratic time (O(n²)): operations are performed in a time that quadratically depends on the size of the collection. Examples of operations with this complexity rating: sorting elements in a List using the Bubble Sort algorithm.

Exponential time (O(2^n)): Operations are performed in a time exponential to the size of the collection. Examples of operations with such a complexity estimate: calculating all subsets of a set.

https://hsto.org/r/w1560/files/364/d7e/419/364d7e41907e453b8e60128cdac459dc.png

➤ What are the levels of complexity of algorithms?

The level of complexity of algorithms is measured in terms of execution time and the number of operations required to complete a task. They are usually classified based on their time complexity (the number of operations that are performed in the worst case) and space complexity (the amount of memory that is required to execute the algorithm).
There are the following levels of algorithm complexity:

O(1) (constant time): Operations are performed in constant time, independent of the size of the input data.

O(log n) (logarithmic time): Execution time grows more slowly than linearly with the size of the input data.

O(n) (linear time): Execution time is proportional to the size of the input data.

O(n log n) (linear logarithmic time): Execution time increases proportionally to the size of the input data multiplied by the logarithm of that size.

O(n²) (quadratic time): Execution time is proportional to the square of the size of the input data.

O(2^n) (exponential time): Execution time grows exponentially with the size of the input data.

https://tproger.ru/articles/computational-complexity-explained/

➤ What is Lock / ReentrantLock

Lock: This is a synchronization mechanism that is used to prevent contention for access to shared resources among multiple threads in a multi-threaded environment. When a thread uses a lock, it gains exclusive access to the resource associated with the lock, and other threads that try to access that resource are blocked until the first thread releases the lock.

ReentrantLock: This is an implementation of the Lock interface in Java that allows a thread to acquire and release a lock repeatedly. It is called “reusable” because it allows a thread to acquire the lock again if that thread already has access to the locked resource.
ReentrantLock allows you to control the locking mechanism more closely than with synchronized blocks, as it provides some additional functionality, such as the ability to suspend and resume threads that are waiting to access a locked resource, the ability to set a timeout to wait for resource access, and the ability to use several conditional variables to control access to the resource.

https://metanit.com/java/tutorial/8.9.php

➤ What are the Backpressure strategies?

Buffer: Buffer all elements that have been sent until the consumer is ready to process them. This strategy can run out of memory if the producer generates elements too quickly or the consumer processes elements too slowly.

Drop: Drops elements that cannot be processed by the consumer. This strategy does not guarantee that all elements will be processed, but it avoids running out of memory.

Latest: Stores only the last element and discards all others. This strategy is suitable for scenarios where only the last element matters.

Error: Signals an error if the thread cannot process the data.

Missing: if the thread cannot process the data, then it will simply skip it, without warning.

➤ What are Delegates in Kotlin

Class delegation: The by keyword in the Derived table of contents, located after the type of the delegated class, indicates that an object b of type Base will be stored inside the Derived instance, and the compiler will generate corresponding methods from Derived from Base, which, when called, will be passed to object b

interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // prints 10
}

Property delegation: There are several main types of properties that we implement manually every time if they are needed. However, it would be much more convenient to implement them once and for all and put them in some library. Examples of such properties:
lazy properties: the value is calculated once, the first time it is accessed
properties for which change events can be subscribed (observable properties)
properties stored in an association list rather than in individual fields
For such cases, Kotlin supports delegated properties.
The expression after by is a delegate: calls (get(), set()) to the property will be processed by this expression. A delegate is not required to implement any interface; it is enough that it has getValue() and setValue() methods with a specific signature

class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thanks for delegating '${property.name}' to me!"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value was assigned to '${property.name} in $thisRef.'")
}
}

Standard delegates

Lazy properties: the first call to get() runs the lambda expression passed as an argument to lazy() and remembers the resulting value, and subsequent calls simply return the calculated value.

val lazyValue: String by lazy {
println("computed!")
"Hello"
}
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue) // computed! Hello
println(lazyValue) // Hello
}

Observable properties: The Delegates.observable() function takes two arguments: the initial value of the property and a handler (lambda) that is called when the property changes. The handler has three parameters: a description of the property that is being changed, the old value and the new value.

class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
// conclusion
// <no name> -> first
// first -> second

Storing properties in an association list:

class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))

https://ru.wikipedia.org/wiki/%D0%A8%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%B4%D0%B5%D0%BB%D0%B5%D0%B3%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F
https://tech-geek.ru/kotlin-delegates-android/
https://kotlinlang.ru/docs/reference/delegated-properties.html

➤ What is Extension in Kotlin

Kotlin allows you to extend the class by adding new functionality.
Extensions don’t actually do any modifications to the classes they extend. When you declare an extension, you are creating a new function, not a new class member. Such functions can be called via dot, applicable to a specific type.
Extensions are statically dispatched, meaning that the extension function called is determined by the type of its expression at compile time, rather than by the type of the expression evaluated during program execution, as with virtual function calls.
https://kotlinlang.ru/docs/reference/extensions.html

fun Any?.toString(): String {
if (this == null) return "null"
return toString()
}

➤ What are the types of errors in Java?

All errors are inherited from the Throwable class

Exception: errors when executing the program, and

Error: system errors when running the JVM
Error does not need to be caught with rare exceptions, since this is a serious error and will cause the program to crash
Exceptions are:

Checked: they must be caught and checked by the compiler, the reason for their occurrence is potential errors in methods
examples: Throwable, Exception, IOException, ReflectiveOperationException

Unchecked: they do not need to be caught and are not checked by the compiler; the reason for their occurrence is errors in the code
examples: RuntimeException, IndexOutOfBoundsException

https://www.fandroid.info/osnovnye-tipy-isklyuchenij-exception-v-java/

➤ How Garbage Collector works and how memory is organized in the JVM

Garbage collector, there are (in HotSpot Oracle JVM):

Serial Garbage Collection: sequential assembly of young and old generations.

Parallel Garbage Collection: default collector in Java 8, works the same as Serial GC, but using multithreading

CMS Garbage Collection: Concurrent Mark-and-Sweep. Makes two short pauses with a complete stop of all threads, these pauses add up to less than the total background build loop. When possible, performs garbage collection in the background. The first pause is called initial mark, at this time the stack is analyzed, after which the heap is traversed in the background, and the mark phase begins. After this, we need to stop the application again and perform a remark to make sure that nothing has changed while we were doing it in the background. And only after that the sweep takes place in the background — cleaning unnecessary areas.

G1 Garbage Collection: The default collector since Java 9, the idea behind G1 is called pause goal. This parameter indicates how long the program can be interrupted during execution for garbage collection, for example, 20 ms once every 5 minutes. The garbage collector does not guarantee that it will work this way, but it will try to work with the desired pauses specified. This makes it fundamentally different from all the garbage collectors we’ve encountered before. The developer has much more flexible control over the garbage collection process. G1 divides the heap into areas (regions) of equal size, for example, 1 megabyte. Next, a set of such regions is dynamically selected, which are called the young generation, while retaining the concept of Eden and Survivor. But the choice occurs dynamically.

Memory areas:

Eden: The heap area in which objects are initially created. Many objects never leave this memory area because they quickly become garbage.
When we write something in the form new Object(), we create an object in Eden.
Refers to the young generation.

Survivor: There are two survivor areas in the memory. Or we can consider that the area of survivors is usually divided in half. It is into this that objects that have survived the “expulsion from Eden” end up (hence its name). Sometimes these two spaces are called From Space and To Space. One of these areas is always empty unless there is a gathering process going on.
From From Space, objects are either removed by GC or moved to To Space — the last place before they become very old and move to Tenured.
Refers to the young generation.

Tenured: An area where surviving objects are found that are considered “old enough” (thus leaving the Survivor area).
The storage is not cleared during a young build.
Typically, by default, objects that have survived 8 garbage collections are placed here.
Refers to old generation.

Permanent memory generation Metaspace and PermGen: Before Java 8, there was a special section: PermGen, which allocated space for internal structures, such as class definitions. PermGen was not part of dynamic memory; ordinary objects never ended up here.
Metadata, classes, interned strings, etc. were stored here — this was a special memory area in the JVM.
Since it is quite difficult to understand the required size of this area, before Java 8 you could often see the java.lang.OutOfMemoryError error. This happened because this area would overflow unless you set enough memory for it, and the only way to determine whether there was enough memory or not was by scientific means.
Therefore, starting with Java 8, it was decided to remove this area altogether and all the information that was stored there is either transferred to the heap, for example, interned strings, or moved to the metaspace area, in native memory. The default maximum Metaspace is not limited by anything other than the native memory limit. But it can optionally be limited by the MaxMetaspaceSize parameter, which is essentially similar to the MaxPermSize of the PermGen ancestor. Metaspace is not cleaned by GC and can be cleaned manually if necessary. You can write and read data in native memory using ByteBuffer and Unsafe.

String Pool: a memory area where strings are stored, its meaning is that strings can be repeated, and in this case one string is written with different references to it.

String text = "text";
// will be created in String Pool
String text = new String("text");
// will be created outside the String Pool in Heap
String text = new String("text").intern();
// will be created in the String Pool using the intern method

There are several types of garbage collections: minor, major and full.
minor garbage collection: cleaned up Eden and Survivor (young generation)
major garbage collection: old generation cleaned up
full garbage collection: everything is cleaned up

https://youtu.be/SNZeMmInVmA
https://vectree.ru/text/137/4/0
http://java-online.ru/garbage-collection.xhtml
https://habr.com/ru/post/269863/
https://urvanov.ru/2018/03/25/%D1%81%D0%B1%D0%BE%D1%80%D1%89%D0%B8%D0%BA-%D0%BC%D1% 83%D1%81%D0%BE%D1%80%D0%B0-g1-%D0%B2-java-9/

➤ What is Assert in Java / Kotlin / JUnit

Language structure for checking parameters.
If the assert condition is false, an AssertionError will be thrown
JUnit and Kotlin have: assertTrue, assertFalse, assertNull, assertNotNull, assertSame, assertNotSame, assertEquals, assertArrayEquals and others, in JUnit they are needed to pass the test, in Kotlin they work similarly to Java
In order for it to work in Java, you need to add the -ea or -enableassertions parameter to jvm options; for Kotlin it is not necessary.
By default, assert does not work in an Android application. In Dalvik, assertions are enabled with the adb shell setprop debug.assert 1 command, but this setting is unreliable and does not work on some versions of Android. In ART, assertions were completely removed.

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/assert.html
https://kotlinlang.org/api/latest/kotlin.test/kotlin.test/
https://en.wikibooks.org/wiki/Java_Programming/Keywords/assert
https://junit.org/junit4/javadoc/4.13/org/junit/Assert.html

➤ What is the Transient modifier

Class properties marked with the transient modifier are not serialized and should not be overridden by equals() and hashCode()

➤ What is inline / noinline / crossinline / reified in Kotlin

inline: Typically, lambda expressions are compiled into anonymous classes. That is, every time a lambda expression is used, an additional class is created. This results in additional overhead for functions that take a lambda as an argument. If you mark a function with the inline modifier, then the compiler will not create anonymous classes and their objects for each lambda expression, but will simply insert its implementation code at the place of the call. Or in other words, it will embed it.
Its main purpose is to increase productivity.
The inline modifier affects both the function and the lambda passed to it: they will both be inlined at the point where it is called.

inline fun <T> lock(lock: Lock, body: () -> T): T {
}

noinline: If you want some lambdas passed to the inline function not to be inlined, then mark them with the noinline modifier.

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
}

crossinline: Some inline functions may call the lambdas passed to them not directly in the function body, but from another context, such as a local object or nested function. In such cases, non-local flow control is also prohibited in lambdas. To indicate this, the lambda parameter must be marked with the crossinline modifier

inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
}

reified: indicated before the type we want to obtain information about inside the function.
Behind the scenes, the compiler will replace the type T with the actual type, so we can get information about it without having to explicitly pass the type to functions.
Reified can also be used in another scenario: to return different types of data from a function.

inline fun <reified T> genericsExample(value: T) {
println(value)
println("Type of T: ${T::class.java}")
}

https://kotlinlang.ru/docs/reference/inline-functions.html
https://bimlibik.github.io/posts/kotlin-inline-functions/

➤ What is Data class in Kotlin

Data class, the compiler automatically generates the following members of this class from the properties declared in the main constructor:
a couple of functions equals()/hashCode(),
the toString() function in the form “User(name=John, age=42)”,
componentN() component functions, which correspond to properties, according to the order in which they are declared,
copy() function
https://kotlinlang.ru/docs/reference/data-classes.html

➤ What is Object in Kotlin

Used to declare a singleton

open class KotlinClass {
open fun method() {}
// just creates an instance
val kotlinClass1: KotlinClass = KotlinClass()
// creates an instance and you can overwrite methods, for this the class and method must be open
val kotlinClass2: KotlinClass = object : KotlinClass() {
override fun method() {}
}
}

and for creating anonymous objects which are a replacement for anonymous inner classes in Java

window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
}
}

https://kotlinlang.ru/docs/reference/object-declarations.html

➤ What is a companion object in Kotlin

Something like a replacement for statics in Java, they are accessed through the name of the class. These helper object members look like static members in other programming languages. In fact, they are members of real objects and can implement, for example, interfaces:

interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}

➤ What is the difference between hot and cold Observables

Cold Observable: Does not send objects until at least one subscriber has subscribed to it;
If an observable has multiple subscribers, then it will broadcast the entire sequence of objects to each subscriber.

Hot Observable: Distributes objects when they appear, regardless of whether there are subscribers;
Each new subscriber receives only new objects, not the entire sequence.

➤ What annotations does Dagger 2 have?

@Inject: An annotation that is placed in the code and queries a dependency from the graph. To deliver dependency to methods and constructors fields, they must be public or internal in Kotlin

@Module: class that contains methods for getting instances of onCreate

@Provides: An annotation with which the method that provides the dependency is annotated.

@Singleton: An annotation that specifies that the annotated method, when initializing a component, will create a single instance of a given dependency. A type of @Scope annotation.

@Component(modules = AppModule::class): the interface is a component and dependencies can be obtained from it; modules can be passed to the annotation

@Scope: An annotation that allows the creation of local and global singletons.

@Qualifier: an annotation that allows you to create your own annotations to separate dependencies with the same interfaces. For example, consider the case where we have two dependencies that provide access to Github-API and Facebook-API via ServerAPI with the same interface.

data class Computer(
val processor: Processor,
val ram: Ram,
val motherBoard: MotherBoard
)
class Processor {}
class Ram {}
class MotherBoard {}
@Component(modules = AppModule::class)
interface AppComponent {
fun computer(): Computer
// or
val computer: Computer
fun inject(activity: MainActivity)
}
@Module
class AppModule {
// the graph looks for these dependencies within itself
@Provides
fun providesComputer(
processor: Processor,
ram: Ram,
motherBoard: MotherBoard
) = Computer(
processor = processor,
ram = ram,
motherBoard = motherBoard
)
@Provides
fun providesProcessor() = Processor()
@Provides
fun providesMotherBoard() = MotherBoard()
@Provides
fun providesRam() = Ram()
}
class MainApp : Application() {
lateinit var appComponent: AppComponent
override fun onCreate(){
super.onCreate()
appComponent = DaggerAppComponent.create()
}
}
val Context.appComponent: AppComponent
get() = when(this){
is MainApp -> appComponent
else -> this.applicationContext.appComponent
}
class MainActivity: Activity(){
@Inject
lateinit var computer: Computer
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
appComponent.inject(this)
}
}

@AssistedInject: it is possible to take dependencies partially from the graph

class Factory() @AssistedInject constructor(
// dependency not from the graph
@Assisted("newsId") private val newsId: String,
// dependency from the graph
private val newsRepository: String,
): ViewModelProvider.Factory
interface Factory {
@AssistedFactory
fun create(@Assisted("newsId") newsId: String): NewsDetailsViewModel.Factory
}
}

@Named(“name”): when several Provides in a graph return the same type, they need to be named

@Provides
@Named("first")
fun providesProcessor() = Processor()
// then
@Inject constructor(@Named("first") val processor: Processor)

@Binds: same as Provides inside a module, but for a module-abstract class

➤ What are the methods in Koin

module: declares a module, i.e. a space to collect all your component definitions.

single: declares a definition singleton of the given type. Koin stores only one instance of this definition.

factory: declares a factory definition of a given type. Koin creates a new instance every time.

get: resolves component dependencies.

createdAtStart: Creates a module/definition instance with the startKoin() function.

override: Definition and module changes must be explicit. You must set override=true for the definition or module that needs to override existing content.

scope: A fixed length of time that an object exists. After this time has elapsed, objects under the influence of scope cannot be injected again (they are thrown out of the container).

koin-android-scope: Helps bind the lifecycle of an Android component to the Koin scope. At the end of its lifecycle, Koin closes its associated scope.

bindScope: will bind the Koin scope to the current lifecycle.

viewModel: Provided by the koin-android-viewmodel project for dependency injection for Android Architecture ViewModel components. Declare the ViewModel class using the viewModel keyword (available in the builder API format so you don’t have to write a constructor). Use this in an Activity or Fragment using by viewModel() or getViewModel().

androidApplication() allow you to restore an Application instance

androidContext() allow you to restore a Context instance

➤ Can a Data Class in Kotlin be inherited from another class and can it be inherited from a Data class

It can be inherited from another class, but it cannot be inherited from a Data class

➤ Is it possible to instantiate an abstract class or interface

You can’t, but you can use an anonymous class and override (Override) the abstract methods of an abstract class or interface in it, in this case an anonymous class is created that implements the original class or implements the interface. It also allows you to override regular methods if they are not final

interface InterfaceName {
void interfaceMethod();
}
InterfaceName interfaceName = new InterfaceName() {
@Override
public void interfaceMethod() {
}
};

Interfaces can also contain methods and properties.

interface MyInterface {
val prop: Int // abstract property
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
fun start(){
foo()

➤ What is the difference between an abstract class and an interface

An abstract class can have ordinary methods, but an interface can only have abstract ones; all variables in the interface are constants, that is, final, you can inherit only from one abstract class, but from many interfaces

https://ziginsider.github.io/images/interview/rfDUm.png

➤ What are the main differences between Dagger/Hilt and Koin

If we want Dagger to provide an instance of some class, all we need to do is add the @Inject annotation to the constructor. Adding this annotation will cause Dagger to generate a Factory for this class at link time. In this case, since the class name is CompositeAdapter, it will generate a class named CompositeAdapter_Factory. This class contains all the information needed to create an instance of the CompositeAdapter class.
To register a dependency in Koin, we do not use any annotations since Koin does not generate any code. Instead, we must provide modules with factories that will be used to create instances of each class we will need in our project.
Since Dagger is a compile-time dependency injection framework, if we forgot to provide any dependency, we will know about our error almost instantly because our project will not build.
The situation is different with Koin. Since it doesn’t generate any code, if we forgot to add a factory for the CompositeAdapter class, the application will build but crash with a RuntimeException as soon as we request an instance of that class. This can happen when the application is launched, so we may notice it right away, but it can also happen later, on some secondary screen or when the user performs some specific action.
There is some benefit to Koin not generating any code: it has a much smaller impact on build times. Dagger needs to use an annotation processor to scan our code and create the appropriate classes. This may take some time and slow down our build.

https://habr.com/ru/company/otus/blog/545222/

➤ How is volatile different from atomic?

The volatile keyword and Atomic* classes in Java are used to provide thread safety when accessing shared memory.

volatile: Ensures that the value of a variable is always read from shared memory and not from the thread cache, preventing synchronization errors. In addition, writing to a volatile variable is also written directly to shared memory, and not to the thread cache.

Atomic* classes: provide atomic operations on variables. That is, they ensure that read and write operations are performed as a single, indivisible action. Atomic* classes are implemented using hardware support mechanisms, so they can be more efficient in some cases than using volatile.

In general, if you only need to ensure thread safety when accessing shared memory, then you can use volatile. If you need to perform an atomic operation on a variable, then you need to use the Atomic* classes.

➤ Where are the Bundles stored?

In the system class ActivityManagerService. For each launched activity, an instance of the ActivityRecord class is created. This class has an icicle field of type Bundle. It is in this field that the state is saved after calling onSaveInstanceState().
When the activity is stopped, the Bundle is sent to the system process via Bindler IPC. Next, the activityStopped() method is called on the ActivityManagerService class, which accepts a Bundle object. This method finds the ActivityRecord corresponding to the stopped activity and writes the resulting Bundle to the icicle field of the ActivityRecord class.

https://itsobes.ru/AndroidSobes/gde-khraniatsia-dannye-onsaveinstancestate/

➤ Name all cases when onSaveInstanceState and onRestoreInstanceState will be called

onSaveInstanceState(): Called before the activity is destroyed, for example when the screen is rotated, the application is minimized, or the device configuration is changed. This method is used to preserve the activity state that may be lost when the activity is destroyed and restored. Also called before the activity is killed by the system in case of memory shortage. In this case, the method is used to save the activity state, which can be restored if necessary.

onRestoreInstanceState(): Called after the activity has been restored from the state saved in onSaveInstanceState(). This method is used to restore the activity state saved in onSaveInstanceState(). Can be called not only after rotating the screen or changing the device configuration, but also in other cases when the activity was destroyed and recreated by the system, for example, when recovering from an application crash.

➤ What is the difference between inheritance and composition

Inheritance and composition are two different approaches to designing classes in object-oriented programming.

Inheritance: It is a mechanism that allows you to create a new class based on an existing class (base class or superclass). In this case, the new class (subclass or derived class) inherits all the properties and methods of the base class, and can also add its own properties and methods.

Composition: It is a mechanism in which objects of one class use objects of other classes to perform their functions. A class that uses another class is called a composite (or component), and a class whose objects are used is called a composite. A composite class contains objects of other classes as its properties and uses their methods to implement its functions.

Differences between inheritance and composition:

Relationship: Inheritance is a “is” relationship where the subclass is an extension of the superclass. Composition is a “has” relationship where an object of one class has a reference to an object of another class.

Flexibility: Composition is more flexible than inheritance. In inheritance, a change to the superclass can affect all subclasses, which is not always desirable. In composition, objects can be easily replaced by other objects if they implement the required interface or abstract class.

Reuse: Composition allows for greater code reuse because objects can be used in different contexts. In inheritance, code reuse is only possible within the context of the inheritance hierarchy.

Barbara Liskov Substitution Principle: If inheritance is used incorrectly, it can violate the Liskov substitution principle, which states that any instance of a class must be replaced by any instance of its subclass without affecting the correctness of the program. In composition, this principle is not violated, since objects of different classes can be replaced by objects that implement the same

➤ What classes are used to access sensors?

SensorManager: entry point for working with sensors and listeners for their events. System service retrieved by name Context.SENSOR_SERVICE;

Sensor: Represents a single sensor. Gives various meta-information (power consumption, accuracy, manufacturer, etc.);

SensorEventListener: interface for implementing event handlers coming from sensors. It implements the logic for processing incoming data;

SensorEvent: a single event from a sensor: data and the accuracy of its measurement.

➤ Describe the architecture of the Android platform

Linux kernel: The heart of Android, which implements interprocess security and low-level memory management.

Hardware Abstraction Layer (HAL): Interfaces for working with hardware. Driver for USB, Bluetooth, OpenGL programming interface. The level that gives Android platform independence.

Android Runtime and native libraries: What the user code does: compilers, garbage collector, bytecode interpreter.

Android Framework: Java API through which the user program interacts with the system. Ensuring the life cycle of system components.

Android Applications: Direct applications, both user and system (calendar, camera, etc.).

➤ What do dpi, dp, sp, dip, px, mm, in and pt mean?

dpi: screen density, number of pixels per inch.

dp, sp: abstract density-independent pixels for element and text sizes. We previously wrote about the relationship between dp and px.

dip: an unmentioned but compiler-understood synonym for dp.

px: size expressed in physical number of screen pixels.

mm, in: physical size on screen, in millimeters and inches respectively.

pt: typographical point (point), also a physical size, equal to 1/72 inches.

➤ What task schedulers are there in Android?

Handler: allows you to execute tasks that have been delayed. It is not a task scheduler in its pure form, because operates at the application process level, not the operating system. If the application is stopped, the code scheduled through the Handler will not be executed. Handler allows you to send messages to other threads with or without delay, as well as process received messages. A Handler is always associated with a Looper, which in turn is associated with some thread. When creating a Handler, you can pass a Looper object to the constructor. If the default constructor is used, the Handler is created on the current thread. If a thread does not have a Looper associated with it, a RuntimeException is thrown when the Handler is created.

AlarmManager: Runs scheduled operations even if the application is stopped. PendingIntents are used as operations. AlarmManager is available with API v1, but does not work in Doze Mode by default. To work in Doze Mode, the setAndAllowWhileIdle() method is used. This method is available with API v23.

JobScheduler: allows you to work in Doze Mode and is available with API v21.
The system groups tasks scheduled through JobScheduler. When the Doze Mode window appears, several tasks are performed at once. This approach saves the device’s battery.

WorkManager: library from Android Jetpack. WorkManager works starting from API v14. Google recommends using WorkManager instead of previous solutions. Under the hood, WorkManager uses JobScheduler on devices with API v23+. On API versions 14–22, GCMNetworkManager or AlarmManager is used.

➤ What are the main Layouts in Android and what is their performance?

LinearLayout: This is the simplest Layout type that focuses on horizontal or vertical layout of child elements. Works quickly on all versions of Android.

RelativeLayout: This is a Layout type that allows child elements to be placed relative to each other or relative to a parent element. Complex RelativeLayout hierarchies can lead to poor performance, so it’s best to avoid using this Layout type if necessary.

GridLayout: This is a Layout type that allows you to create tables and arrange elements at the intersection of rows and columns. GridLayout can be very effective if used in the right context.

ConstraintLayout: This is a Layout type that allows you to create complex View hierarchies using constraints. ConstraintLayout can be effective if configured correctly, and can even outperform RelativeLayout.

FrameLayout: This is a simple Layout type that can only contain one child element. It works quickly, but is not suitable for placing multiple elements.

TableLayout: Designed to lay out elements in a table format. It can be useful for screens with a lot of data or for displaying a list.

GridLayout: Allows you to lay out elements in a grid. It is especially useful for screens with a lot of data, such as list or table screens.

ScrollView: Used to create scrollable content. This type of layout can be useful for screens with a lot of text or images.

➤ What is Spannable?

Spannable: This is an interface that characterizes text that has stylistic markup.
For example, using Spannable you can create text, part of which is colored in a different color.
The Spannable.setSpan() method accepts an arbitrary object of type Object, which is used for layout. This method does not throw exceptions. Classes that implement the Spannable interface must ignore unsupported markup objects.
ForegroundColorSpan is one example of a markup class. This class is used to change the color of text.

➤ What is Looper?

Android applications run in a virtual machine that uses the Java Runtime. In Java, all work is done in threads. The thread exits when the run() method is executed. In Android, the main (UI) thread is not always busy performing any task and is often waiting for user actions or system events. To implement this behavior, Android uses three entities that work together: Looper, MessageQueue, and Handler.
Looper starts a message loop associated with a thread. The thread runs until its associated Looper is stopped.
To create a Looper, the static method Looper.prepare() is called. The created Looper will be associated with the thread on which this method is called. To start Looper, the static method Looper.loop() is used.

Between calls to the prepare() and loop() methods, a Handler is usually created that will process messages arriving in the MessageQueue of the Looper. To stop the Looper, use the quit() or quitSafely() method. The difference between these methods is that quit() stops the Looper immediately, while quitSafely() finishes processing messages that have already been added to the queue. These methods are not static and are called on the Looper class instance. You can get the Looper of the current thread using the static method Looper.myLooper(). The Looper UI of a thread can be obtained using the Looper.getMainLooper() method.

➤ What is the difference between build type, flavor and build variant?

Build Type: Used to set build settings, such as whether proguard will be used and what certificate the apk will be signed with. The most commonly used build types are debug and release. The build type is specified by the buildTypes parameter in the gradle file.

buildTypes {
debug {
applicationIdSuffix ".debug"
debuggable true
minifyEnabled false
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

Flavor: Used to define app builds with different functionality. These could be paid features, different target APIs, different resources. Set by the productFlavors parameter in the gradle file.

productFlavors {
free {
dimension "version"
applicationId "ru.itsobes.flavors.free"
resValue "string", "flavored_app_name", "Free It Sobes App"
}
paid {
dimension "version"
applicationId "ru.itsobes.flavors.paid"
resValue "string", "flavored_app_name", "Paid It Sobes App"
}
}

Build Variant: This is a combination of build type and flavor. For the build type and flavor described above, four build variants are created: freeDebug, freeRelease, paidDebug, paidRelease.

➤ Describe the structure of an Intent object

Main attributes:

action: a string variable specifying the action to be performed.

data: a Uri class object that describes the data on which the action will be performed.
For example, an Intent with action == ACTION_CALL and data == Uri.parse(“tel:$number”) makes a call to the specified phone number.

Additional attributes:

category: gives additional information about the action that will be performed.

type: Explicitly specifies the MIME type of the data that is passed to data.

component: specifies the class name of the component (eg Activity) that should be launched. If you set the component attribute, then all other attributes become optional.

extras: A Bundle object that contains additional data passed to the component with the intent.

➤ Can an abstract class inherit from a regular class?

Yes, it can also overwrite methods, making them abstract, an interface cannot

class One{
void one(){}
}
abstract class Two extends One {
@Override
abstract void one();
}
class Three extends Two {
@Override
void one() {}
}

➤ How to use getters (get) and setters (set) in Kotlin

var stringRepresentation: String
get() = this.toString()
set(value) { setDataFromString(value) }
var setterVisibility: String = "abc"
private set // setter has private access and standard implementation

➤ What are the loops in Java and Kotlin

Java:

for (int i = 0; i < 10; i++){
}
for ( ; ; ) {
}
for (String text : array) {
}
do {
} while (true)
while (true){
}
list.stream().forEach((k) -> {
})
list.stream().forEach(System.out::println);

Kotlin:

for (value in array){
}
for (x in 1..5){
}
for (x in 9 downTo 0 step 3) {
}
list.forEach { print(it) }
val items = setOf("one", "two", "three")
when {
"one" in items -> println("one")
"two" in items -> println("two")
}
// checking the element in the collection will output one

➤ What are the bitwise (bitwise) operations in Java and Kotlin

In Java:

byte b = 7; // 0000 0111
short s = 7; // 0000 0000 0000 0111
To write signed numbers in Java, two’s complement code is used, in which the most significant digit is signed. If its value is 0, then the number is positive, and its binary representation is no different from the unsigned number. For example, 0000 0001 in decimal system is 1. If the most significant digit is 1, then we are dealing with a negative number. For example, 1111 1111 represents -1 in decimal. Accordingly, 1111 0011 represents -13.
&: logical multiplication
int a1 = 2; //010
int b1 = 5; //101
System.out.println(a1 & b1); // result 0
int a2 = 4; //100
int b2 = 5; //101
System.out.println(a2 & b2); // result 4
|: logical addition
int a1 = 2; //010
int b1 = 5; //101
System.out.println(a1 | b1); // result 7–111
int a2 = 4; //100
int b2 = 5; //101
System.out.println(a2 | b2); // result 5–101
^: logical exclusive OR
~: logical negation
a<<b: shifts the number a to the left b places. For example, the expression 4<<1 shifts the number 4 (which is 100 in binary) one place to the left, resulting in the number 1000 or the number 8 in decimal.
a>>b: shifts the number a to the right by b digits. For example, 16>>1 shifts the number 16 (which is 10000 in binary) one place to the right, resulting in 1000, or the number 8 in decimal notation.
a>>>b: unlike previous types of shifts, this operation is an unsigned shift — it shifts the number a to the right by b digits. For example, the expression -8>>>2 will be equal to 1073741822.

In Kotlin:

shl(bits): sign-sensitive left shift (<< in Java)
shr(bits): sign-sensitive right shift (>> in Java)
ushr(bits): unsigned right shift (>>> in Java)
and(bits): bitwise AND
or(bits): bitwise OR
xor(bits): bitwise exclusive OR
inv(): bitwise negation

➤ What is ClassLoader

There are three standard loaders in Java, each of which loads a class from a specific location:

Bootstrap: Basic loader, also called Primordial ClassLoader. Loads standard JDK classes from the rt.jar archive

Extension ClassLoader: extension loader. Loads extension classes, which are located in the jre/lib/ext directory by default, but can be set by the java.ext.dirs system property

System ClassLoader: system loader. Loads application classes defined in the CLASSPATH environment variable
Each loader, except the base one, is a child of the abstract class java.lang.ClassLoader. For example, the implementation of the extension loader is the sun.misc.Launcher$ExtClassLoader class, and the system loader is sun.misc.Launcher$AppClassLoader. The base loader is native and its implementation is included in the JVM.
You can also implement your own ClassLoader, for this you need to extend ClassLoader and override its methods

public abstract class ClassLoader {
public Class<?> loadClass(String name);
protected Class<?> loadClass(String name, boolean resolve);
protected final Class<?> findLoadedClass(String name);
public final ClassLoader getParent();
protected Class<?> findClass(String name);
protected final void resolveClass(Class<?> c);
}

When a class is launched, it undergoes verification so that values in the bytecode cannot be changed using a hex editor, thereby breaking the logic.
If the class does not pass verification, the error java.lang.VerifyError is thrown

https://javarush.ru/groups/posts/646-kak-proiskhodit-zagruzka-klassov-v-jvm
https://youtu.be/QkJwSUpRGpM

➤ What is SecurityManager

The java.lang.SecurityManager class allows applications to implement security policies. Without SecurityManager, rights are not limited. SecurityManager allows an application to determine, before performing a possibly unsafe or sensitive operation, what the operation is and whether it is being attempted in a security context that allows the operation to be performed. The application can allow or deny the operation.
SecurityManager has many check… methods for checking.
For example: checkAccept throws a SecurityException if the calling thread is not allowed to accept a socket connection from the specified host and port number. Usually permissions are in the java.policy or default.policy file

SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccept (String host, int port);
}

You can create your own file with permissions, and then either write -Djava.security.policy=src/…/my.policy in vm options or

System.setProperty("java.security.policy", "src/…/my.policy");
System.setSecurityManager(new SecurityManager());

https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
https://coderlessons.com/tutorials/java-tekhnologii/izuchite-paket-java-lang/klass-java-lang-securitymanager
https://youtu.be/ZfN7fSvkLVs

➤ What is JavaCompiler

An interface that allows you to compile code from a program

JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
int resultCode = javaCompiler.run(null, null, null ,”path/test.java”);

➤ How to run JavaScript from Java

Before starting, check if the JavaScript engine is installed

ScriptEngine engine = new ScriptEngineManager(null).getEngineByName("JavaScript");
// or
engine = new ScriptEngineManager(null).getEngineByName("js");
// or
engine = new ScriptEngineManager(null).getEngineByName("nashorn");
String code = "var q = 0; q + 1";
Object o = engine.eval(code);
System.out.println(o);

https://youtu.be/Ar_K1aJrCl4

➤ What methods are there in RxJava

from(): creates an Observable from an array or collection

map(): transforms each element emitted by the Observable and emits the modified element

flatMap(): takes as input the data emitted by one Observable and returns the data emitted by another Observable, thus replacing one Observable with another

concatMap(): produces a result similar to FlatMap(), only the sequence of emitted data changes. ConcatMap() maintains the order of data emission and waits for the current Observable to execute. Therefore, it is better to use it when it is necessary to ensure the order of tasks. It should be remembered that ConcatMap() takes longer to execute than FlatMap()
swithMap(): This is completely different from FlatMap() and ConcatMap(). It is best suited if you want to ignore intermediate results and consider the last one. SwitchMap unsubscribes from the previous Observable source whenever a new element starts emitting data, thereby always emitting data from the current Observable

buffer(int): collects elements and, as the specified number accumulates, sends them further in one package, for example in the form of a List

take(int): will take only the specified number of first elements from the sequence passed to it and form a new sequence from them

skip(int): will skip the first elements

distinct(): will filter out duplicates

filter(): can filter out only the necessary elements. To do this, you need to create a function that will describe the filtering algorithm
concat(observable, observable): used to concatenate multiple observers for sequential execution

.concat(
Observable.just(1, 2),
Observable.just(3, 4, 5),
Observable.just(7, 8, 9))
// 1 2 3 4 5 6 7 8 9

merge(observable, observable): used to merge multiple observers for parallel execution

.merge(
Observable.intervalRange(0, 3, 1, 1, TimeUnit.SECONDS),
Observable.intervalRange(3, 3, 1, 1, TimeUnit.SECONDS))
// 0 3 1 4 2 5

mergeWith(observable): will merge elements from two Observables into one Observable

zip(): Used to compress multiple observers into one operation

.zip(
Observable.just(1, 2),
Observable.just(7, 9),
(int1, int2) -> int1 + int2))
// 8 11

takeUntil(): will take elements until it finds an element that meets a certain condition. We need to formalize this condition as a function

all(): allows you to find out if all elements satisfy a specified condition. We need to formalize the condition as a function
startWith(): used to add data before sending the element
concatDelayError/mergeDelayError(): When using the concat and merge operators, if one of the observers emits an onError event, it immediately terminates the events of the other observers. If you want the onError event to be delayed until other observers complete, you can use the appropriate concatDelayError or mergeDelayError operator
combineLatest(): Used to combine the latest elements on the same timeline

.combineLatest(
Observable.just(1, 2, 3),
Observable.intervalRange(0, 5, 1, 1, TimeUnit.SECONDS),
(int1, int2) -> int1 + int2))
// 3 4 5 6 7

reduce(): used to combine all elements together

.just(1, 2, 3)
.reduce((last, item) -> {
// First do 1 + 2, then add the result of 1 + 2 to 3 and finally print 6, which is equivalent to grouping three elements together
return last + item;
})
// 6

count(): used to count the number of elements sent by the observer

collect(): The collect and reduce operators are similar, but they need to define their own collection container and collection logic

.just(1, 2, 3)
.collect(new Callable<ArrayList<Integer>>() {
@Override
public ArrayList<Integer> call() throws Exception {
eturn new ArrayList<>();
}
}, new BiConsumer<ArrayList<Integer>, Integer>() {
@Override
public void accept(ArrayList<Integer> list, Integer value) throws Exception {
System.out.println("add element " + value);
list.add(value);
}
})
// add element 1
// add element 2
// add element 3
// accept [1, 2, 3]

range(3, 3): will give a sequence of numbers, here — 3 4 5

interval(10, TimeUnit.MILLISECONDS): returns a sequence of long numbers starting from 0. We can specify the time interval at which the numbers will arrive.

fromCallable(): If you have a synchronous method that you need to make asynchronous, then the fromCallable operator will help you

private int longAction(String text) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Integer.parseInt(text);
}
class CallableLongAction implements Callable<Integer> {
private final String data;
public CallableLongAction(String data) {
this.data = data;
}
@Override
public Integer call() throws Exception {
return longAction(data);
}
}
Observable.fromCallable(new CallableLongAction("5"))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
log("onNext " + integer);
}
});

doOnNext(): allows us to add some additional action that happens whenever we receive a new data item

➤ What is Optional in Java and Kotlin

Optional in Java: This is a concept that was added in Java 8 to handle possible null values.

Optional<String> text = Optional.empty();
text = Optional.of("123");
if(text.isEmpty()) {
System.out.println(text.get());
}

override fun getData(): Optional<Data> {
val file = getFile()
return if (!file.exists() || !file.isFile) {
Optional.empty()
} else {
Optional.of(file)
}
}

In Kotlin, instead of Optional, the “Nullable” construct is used.
In Kotlin, Nullable is used to denote variables that can contain null values. Operations with Nullable variables should use safe navigation operators (safe call operator — ?. and elvis operator — ?:) to avoid exceptions when attempting to access a null value.
Optional in Kotlin also has a special data type called “Optional” or “Maybe” which can be used in some situations where you want to explicitly indicate that a variable can contain null or non-null value. The Optional type is created using the “Nullable” keyword as a modifier

➤ What are Method References

When only one method call is needed, then (x::toString) is the same as (x -> x.toString())

➤ What is ByteBuffer and Unsafe

A class with which you can write or read data from native memory (not heap, faster). This memory is not cleared by Garbage Collector and must be cleared yourself.

ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // size in bytes
byteBuffer.put(new byte[]{1, 2, 3}); // write to memory
byteBuffer.flip(); // delete all unfilled ones (everything after 1 2 3)
byteBuffer.position(0); // move the cursor to the starting position
byte[] bytes = new byte[byteBuffer.remaining()]; // size == 3 after flip or == 1024 without it
byteBuffer.duplicate().get(bytes); // get content
byteBuffer.clear(); // don't forget to clear
// now bytes == 1 2 3

Unsafe allows you to work with memory directly

public class Main {
public static void main(String[] args) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe"); // using reflection we get the field
field.setAccessible(true); // allow access
Unsafe unsafe = (Unsafe) field.get(null); // get the field and cast it to the class
// try to write int
long startAddressInDDR = unsafe.allocateMemory(1024L); // allocate memory in ddr in bytes
unsafe.putInt(startAddressInDDR, 123);
System.out.println(unsafe.getInt(startAddressInDDR));
unsafe.freeMemory(startAddressInDDR); // clear the address
// then a new instance of the class))
Example example = (Example) unsafe.allocateInstance(Example.class);
System.out.println(example.name); // null although the class has a constructor where the name needs to be passed, and the name is initialized
example.name = "name";
System.out.println(example.name); // name
}
}
class Example{
Example(String name){
this.name = name;
}
public String name = "1";
}
// Kotlin
fun main(args: Array<String>) {
val field: Field = Unsafe::class.java.getDeclaredField("theUnsafe")
field.isAccessible = true
val unsafe: Unsafe = field.get(null) as Unsafe
val example: Example = unsafe.allocateInstance(Example::class.java) as Example
val name: String = example.name // according to the syntax there cannot be null
println(name) // but here is null
example.name = "name"
println(example.name) // name
}
data class Example(var name: String = "1")

➤ What is callback, function types and Unit in Kotlin and how is it used

Unit is the same as void in Java, it does not return anything.

fun main(args: Array<String>) {
one { variable ->
print(variable)
}
two { variable ->
print(variable)
return@two "4"
}
three {
return@three "5"
}
four({ variableOne ->
print(variableOne)
}, { variableTwo ->
print(variableTwo)
})
val listenerOne: (() -> Unit) = {
print("8")
}
listenerOne.invoke()
val listenerTwo: ((String) -> Unit) = { variable ->
print(variable)
}
listenerTwo.invoke("9 ")
val listenerThree: ((String) -> String) = { variable ->
variable
}
val result = listenerThree.invoke("10 ")
print(result)
}
fun one(callback: (String) -> Unit) {
// pass it to the lambda and pass a String to it, do not expect anything back (Unit)
callback("1")
}
fun two(callback: (String) -> String) {
// pass it to the lambda and pass a String to it, expect a String back
callback("2")
print(callback("3 "))
}
fun three(callback: () -> String) {
// call the lambda but don’t pass anything into it, expect a String back
print(callback())
}
fun four(callbackOne: (String) -> Unit, callbackTwo: (String) -> Unit) {
// returns 2 callbacks
callbackOne("6 ")
callbackTwo("7 ")
}
// 1 2 3 4 5 6 7 8 9 10

or usage example in LiveData

val liveData: SingleLiveEvent<Unit> by lazy {
SingleLiveEvent<Unit>()
}
// then we can send the event to liveData without passing anything there
liveData.postValue(Unit)

Functional types: Kotlin uses a family of function types such as (Int) -> String for declarations that are part of functions: val onClick: () -> Unit = ….
All function types have a list of parameter types, enclosed in parentheses, and a return type: (A, B) -> C denotes a type that provides the function with two accepted arguments of type A and B, and also returns a value of type C. List with types parameters can be empty, as in () -> A. The return type Unit cannot be omitted.
Function types can have an additional type, receiver, which is specified in the declaration before the dot: type A.(B) -> C describes functions that can be called on a receiver object A with a parameter B and a return value C. Function literals with a receiver object are often used in conjunction with these types.
Suspending functions belong to a special kind of function types that have a suspend modifier in their declaration, for example, suspend () -> Unit or suspend A.(B) -> C.
A function type declaration can also include named parameters: (x: Int, y: Int) -> Point. Named parameters can be used to describe the meaning of each parameter.
To indicate that a function type may be nullable, use parentheses: ((Int, Int) -> Int)?.
Using parentheses, functional types can be combined: (Int) -> ((Int) -> Unit).
The arrow in the ad is right-associative, i.e. the declaration (Int) -> (Int) -> Unit is equivalent to the declaration from the previous example, not ((Int) -> (Int)) -> Unit.
You can also give a function type an alternative name using type aliases:
typealias ClickHandler = (Button, ClickEvent) -> Unit
Creating a Function Type:
There are several ways to obtain an instance of a function type:
Using a code block inside a function literal in one of the forms:
lambda expression: { a, b -> a + b },
anonymous function: fun(s: String): Int { return s.toIntOrNull() ?: 0 }
Using an instance of a custom class that implements a function type as an interface:

class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()

https://kotlinlang.ru/docs/reference/lambdas.html

➤ What is lambda in Java

Represents a set of instructions that can be isolated into a single variable and then called repeatedly at different places in the program.
A lambda expression does not execute on its own, but forms an implementation of a method defined in an interface.
An interface should contain only one method with no implementation.

interface Interface {
String method(String parameter);
}
public static void main(String[] args) {
// executes the "method" method in an anonymous class, which takes a parameter as input, prints it and returns another parameter
Interface one = (String parameter) -> {
System.out.print(parameter);
return "2";
};
System.out.print(one.method("1 "));
}
// 12

➤ What Dispatchers are there in coroutines

Default: This is the default if the manager type is not explicitly specified. This type uses a shared pool of shared background threads and is suitable for computations that do not involve I/O operations (file, database, network) and that are CPU intensive.

suspend fun main() = coroutineScope{
launch { }
// or
launch(Dispatchers.Default) { }
}

Main: main thread

Unconfined: The coroutine is not clearly assigned to a specific thread or thread pool. It runs in the current thread until the first suspension. After resuming work, the coroutine continues to work in one of the threads, which is not strictly fixed. Kotlin language developers generally do not recommend using this type.

suspend fun main() = coroutineScope{
launch(Dispatchers.Unconfined) {
// Thread.currentThread().name == main
delay(500L)
// Thread.currentThread().name == kotlinx.coroutines.DefaultExecutor
}
}

IO: Uses a shared pool of threads, created as needed, and is designed to perform I/O operations (such as file operations or network requests).

newSingleThreadContext and newFixedThreadPoolContext: allow you to manually set the thread/pool for executing the coroutine

suspend fun main() = coroutineScope{
launch(newSingleThreadContext("MyThread")) {
// Thread.currentThread().name == MyThread
}
}

➤ What Scope are there in coroutines?

CoroutineScope: Monitors any coroutine created using launch or async (these are extension functions in CoroutineScope). Current work (running coroutines) can be canceled by calling scope.cancel() at any time.
You need to create a CoroutineScope whenever you want to run and monitor the lifecycle of coroutines in a specific layer of your application. Some platforms, such as Android, have KTX libraries that already provide CoroutineScope in certain lifecycle classes, such as viewModelScope and lifecycleScope.
When created, CoroutineScope takes a CoroutineContext as a parameter to its constructor. You can create a new scope and coroutine

fun main(args: Array<String>) {
val mainScope: CoroutineScope = MainScope()
}
suspend fun method() = coroutineScope {
}
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
}

GlobalScope: The life cycle of a coroutine is limited only by the life cycle of the entire application. marked with the @DelicateCoroutinesApi annotation. You need to be very careful when using the GlobalScope API because you can easily leak resources or memory. Coroutines running in GlobalScope do not follow the principle of structured concurrency. If they freeze or slow down (for example, due to low network bandwidth), they still continue to work, consuming resources. If you are using GlobalScope.launch to launch multiple parallel operations, they should be grouped using coroutineScope. The global CoroutineScope is not associated with any job. This option is used to run top-level coroutines that run for the entire life of the application. Active coroutines running in GlobalScope do not prevent the process from stopping. They are similar to daemon threads in Java.

suspend fun method() {
coroutineScope {
launch { }
launch { }
}
}

runBlocking { }: blocking code execution

fun main(args: Array<String>) {
print("1 ")
val result = runBlocking {
print("2 ")
delay(1000L)
print("3 ")
return@runBlocking "5 "
}
print("4 ")
print(result)
print("6 ")
// 1 2 3 4 5 6
}

viewModelScope: Binding to the ViewModel lifecycle

viewModelScope.launch {
}

lifecycleScope: defined for each Lifecycle object

viewLifecycleOwner.lifecycleScope.launch {
whenCreated {
}
whenStarted {
}
whenResumed {
}
}

https://nuancesprog.ru/p/7397/
https://developer.android.com/topic/libraries/architecture/coroutines
https://blog.jetbrains.com/ru/kotlin/2021/06/kotlin-coroutines-1-5-0-released/

➤ What is CoroutineContext / Job / CoroutineDispatcher / CoroutineName / CoroutineExceptionHandler

CoroutineContext: a set of elements that define the behavior of a coroutine.
It consists of:

Job: Manages the life cycle of a coroutine.
A Job can go through many states: new, active, completed, completed, cancelled, and canceled. Although we don’t have access to the states themselves, we can access the Job properties: isActive, isCancelled, and isCompleted.

CoroutineDispatcher: Dispatches work to the appropriate thread.

CoroutineName: The name of the coroutine, useful for debugging.

CoroutineExceptionHandler: Handles uncaught exceptions.

https://nuancesprog.ru/p/7397/

➤ What are higher order functions

Higher-order functions: Functions that can take other functions as arguments or return functions as a result. This means that higher-order functions can be treated as first-class objects in a programming language.
In languages that support higher-order functions, functions can be used as values and passed to other functions. For example, a higher-order function can take another function as an argument and apply it to other data.
Higher-order functions can be used to create more abstract and flexible algorithms and APIs. They can simplify code, reduce duplication, and improve readability.

// example of a higher order function that takes a function as an argument
fun applyOperation(value: Int, operation: (Int) -> Int): Int {
return operation(value)
}

// example of using a higher order function
val result = applyOperation(5) { value -> value * 2 } // result will be 10

In this example, the applyOperation function takes an operation argument, which is a function that takes one argument of type Int and returns a result of type Int. Inside the applyOperation function, operation is called with the value argument and the result is returned from the applyOperation function.

➤ What is the difference between the structural, functional and object-oriented paradigms in programming

There are 3 paradigms in programming, they were discovered between 1958 and 1968. Each paradigm imposes restrictions on the code; paradigms tell us what not to do.

Structured programming imposes restrictions on direct transfer of control (for example, using the goto / jump statement)

Object-oriented programming imposes restrictions on indirect transfer of control

Functional programming imposes restrictions on assignment

see Robert Martin, Clean Architecture

➤ What is Mutex, Monitor, Semaphore

Semaphore: A type of locking that limits the number of threads that can enter a given section of code.

Mutex: An object for thread synchronization, attached to every object in Java.
Can have 2 states — free and busy. The mutex state cannot be controlled directly.
If another thread needs access to a variable protected by a mutex, that thread will block until the mutex is released.
It differs from a semaphore in that only the thread that owns it can release it.
In a block of code that is marked with the word synchronized, the mutex is captured.
The purpose of a mutex is to protect an object from being accessed by threads other than the one that acquired the mutex.
A mutex protects data from being corrupted by asynchronous changes (race conditions), but if used incorrectly it can cause other problems such as deadlocks or double locks.

Monitor: a high-level mechanism for interaction and synchronization of processes that provides access to non-shared resources.
Creates a protection mechanism for the implementation of synchronized blocks.

https://gist.github.com/vchernogorov/eb127203ca40cb0e84b46c2b8bb6790f
https://ru.wikipedia.org/wiki/%D0%9C%D1%8C%D1%8E%D1%82%D0%B5%D0%BA%D1%81

➤ What layers exist in pure architecture and what are they responsible for?

Typically, the application is divided into 3 layers, which can be located in different modules — Presentation, Domain, Data:

Presentation:

UI, ViewModel and other similar classes
Domain/Business Logic:
UseCase interfaces and their implementations
repository interfaces (no implementation)
local data models that the application works with

Data:

implementation of repository interfaces
network data models
database models
Newtork APIs
Typically dependencies in Gradle are configured like this:
Presentation:
implementation project(“:domain”)
implementation project(“:data”)

Data:

implementation project(‘:domain’)

Domain:
//no

https://user-images.githubusercontent.com/21035435/69536839-9f4c8e80-0fa0-11ea-85ee-d7823e5a46b0.png
https://pbs.twimg.com/media/DG38WLrXkAEM-R4.jpg
https://proandroiddev.com/creating-clean-architecture-multi-project-mvp-app-34d753a187ad

➤ What is the @JvmStatic annotation

Indicates that an additional static method should be generated from this element if it is a function. If this element is a property, then additional static getter/setter methods should be generated.

➤ Why you shouldn’t subscribe to MutableStateFlow in ViewModel

So that it is impossible to change the value from View

private val _showMessageFlow = MutableStateFlow<String?>(null)
val showMessageFlow: StateFlow<String?> = _showMessageFlow
// by analogy with
var name: String? = null
private set

➤ What are Deferred coroutines in Kotlin

Deferred coroutines: This is one of the mechanisms for working with asynchronous operations in Kotlin using coroutines. They are objects that represent the result of an asynchronous operation.
Deferred coroutines are created using the async function and can be used to perform long-running operations on a background thread while leaving the application’s main thread free.
Once a deferred coroutine completes its work, its result can be retrieved using the await() function. Unlike join(), which blocks the calling thread until the coroutine completes execution, await() does not block the calling thread, but returns only when it is ready.
Here is an example of using deferred coroutines in Kotlin:

fun loadDataAsync(): Deferred<List<Data>> = GlobalScope.async {
// loading data from the network or database in a background thread
}

fun displayData() {
GlobalScope.launch {
// launch coroutine to load data
val deferredData = loadDataAsync()
// perform some actions on the main thread
// get the result of executing the deferred coroutine
val data = deferredData.await()
// process the data
}
}

➤ What are lazy (LAZY) coroutines in Kotlin

LAZY coroutines are a way to create lazy coroutines in Kotlin using the lazy and async function from the kotlinx.coroutines library.
The main difference between deferred coroutines created using the async function and LAZY coroutines is the moment the coroutine object is created. Deferred coroutines are created immediately when the async function is called, whereas LAZY coroutines are created only the first time they are called.
Typically, LAZY coroutines are used in cases where there is no need to immediately start executing the coroutine, for example, when we do not know whether its execution will be necessary at all. This allows you to avoid unnecessary resource costs and speed up the application.
Here is an example of creating a LAZY coroutine:

val lazyCoroutine: Lazy<Deferred<String>> = lazy {
GlobalScope.async {
// executing the coroutine in a background thread
"Hello from coroutine!"
}
}

fun main() {
// perform actions in the main thread
// getting the result of the coroutine execution
val result = runBlocking { lazyCoroutine.value.await() }
// processing the result
println(result)
}

// or

fun main() = runBlocking<Unit> {
val lazyCoroutine = launch(start = CoroutineStart.LAZY) {
println("Coroutine is executing")
}
// perform other actions on the main thread
lazyCoroutine.start() // start the coroutine
// perform other actions on the main thread
lazyCoroutine.join() // waiting for the coroutine to complete
}

In this example, we create a lazy coroutine using the launch function and pass it the parameter start = CoroutineStart.LAZY. We then perform some other actions on the main thread, and only then do we call the start() method of our deferred coroutine to start its execution.
It’s important to understand that if we don’t call the start() method of our deferred coroutine, it will never execute.
We can also use the CoroutineStart.LAZY parameter with the async and withContext functions. In this case, a deferred coroutine is created that returns the result of the calculation. We can call await() on this coroutine to get the result of the execution.

https://kotlinlang.org/docs/composing-suspending-functions.html#lazily-started-async

➤ Can AndroidManifest contain multiple main activities?

By main activity we mean an activity whose intent-filter contains ACTION_MAIN and CATEGORY_LAUNCHER.
You can add several main activities to AndroidManifest. An icon will appear for each activity in the application menu. By default, the application icon and name specified in the icon and label attributes of the application element in the manifest will be used. These attributes can be overridden in the activity element.
This mechanism can be useful for libraries that are used in debug/testing builds. This is how the leakcanary library adds an icon to launch its activity from the application menu.

https://itsobes.ru/AndroidSobes/mozhet-li-androidmanifest-soderzhat-neskolko-main-activity/

➤ What is a multi-date declaration

The component functions generated for data classes allow them to be used in multi-declarations.

val jane = User("Jane", 35)
val(name, age) = jane
println("$name, $age years of age")
// prints "Jane, 35 years of age"

➤ What’s the difference in Build Type, Flavor, Build Variant

In Android Studio, an application can have multiple assemblies for different purposes. Build Type, Flavor and Build Variant are part of the app’s build configuration in Android Studio.

Build Type: Allows you to define which build type to use. Android Studio comes with two predefined build types: debug and release. The debug build type is for developing and testing the application, while the release build type is for the final build of the application before publishing.

Flavor: Allows you to create variants of the application for different purposes, such as different versions for different languages, for different markets, etc. Flavor can have an applicationId property, which allows you to define a unique identifier for each application variant.

Build Variant: This is a combination of Build Type and Flavor. That is, for each assembly type, several variants can be created with different properties, such as batch name, assembly settings, etc.
For example, if we have two build types — debug and release, and two Flavors — free and paid, then we can have four different application options: debugFree, debugPaid, releaseFree and releasePaid. Each of these options can have its own build properties, such as different resource files and API keys.

➤ What is Fragment Result API

In some cases, you may need to pass a nonce between two fragments, or between a fragment and its host activity. For example, you could have a fragment that reads QR codes, passing data back to the previous fragment. Since fragment 1.3.0-alpha04, every FragmentManager implements FragmentResultOwner. This means that a FragmentManager can act as a central repository for fragment results. This change allows components to communicate with each other by setting the results of fragments and listening to those results, without requiring these components to have direct references to each other.
To pass data back to fragment A from fragment B, first set a results listener on fragment A that receives the result. Call setFragmentResultListener() on A FragmentManager, as shown in the following example:

// Send the result to another fragment
val someData = "123"
SomeFragmentManager?.setFragmentResult(
BUNDLE_KEY,
bundleOf(STRING_KEY to someData)
)

// get the result in another fragment
SomeFragmentManager?.setFragmentResultListener(
BUNDLE_KEY, viewLifecycleOwner
) { _, bundle ->
bundle.getString(STRING_KEY)?.let(viewModel::setString)

➤ What are Kotlin Channels

Kotlin Channels: This is a component provided by Kotlin Coroutines that allows values to be passed between coroutines asynchronously.
Channels are a higher-level abstraction than primitive synchronized objects such as locks and semaphores. They allow values to be passed between coroutines while maintaining correct order and safety in a multi-threaded environment.
Kotlin Channels are similar to message queues and have the following basic operations: send and receive. It is also possible to close the channel, which stops data transfer.
Additionally, Kotlin Channels provide various buffering operations such as limiting the buffer size and timeout for receiving a message.
In general, Kotlin Channels allow you to more efficiently and securely organize asynchronous data transfer in your application.

fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
}
repeat(5) { println(channel.receive()) }
println("Done!")
}

In this example, we created a Channel using Channel<Int>(), which allows us to pass integers. We then launched a coroutine that sends five messages to the channel using the send() method. In the main thread, we receive messages from the channel using the receive() method and print them to the console.
Expected output: 1 4 9 16 25 Done!
Here we have used the runBlocking function to start the main thread. This is necessary because the launch function launches the coroutine in a new thread

https://metanit.com/kotlin/tutorial/8.6.php

➤ What is DataStore Library

SharedPreferences has several disadvantages:
A synchronous API that may appear safe to call on the UI thread, but actually performs disk I/O operations.
There is no error reporting mechanism, transaction API, and much more.

DataStore is a replacement for SharedPreferences that addresses most of these shortcomings. DataStore includes a fully asynchronous API using Kotlin Coroutines and Flow. Gives us a very simple and convenient tool for data migration. Ensures data consistency and handling of corrupted data. (SharedPreferences does however have an OnSharedPreferenceChangeListener which allows you to listen for changes by key)

https://developer.android.com/topic/libraries/architecture/datastore

implementation "androidx.datastore:datastore:1.0.0"
implementation "androidx.datastore:datastore-core:1.0.0"

implementation 'androidx.datastore:datastore-preferences:1.0.0'
implementation "androidx.datastore:datastore-preferences-core:1.0.0"

private const val DATA_STORE_KEY = "DATA_STORE_KEY"

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(DATA_STORE_KEY)

suspend fun Context.writeString(key: String, value: String) {
dataStore.edit { pref -> pref[stringPreferencesKey(key)] = value }
}

fun Context.readString(key: String, defaultValue: String): Flow<String> {
return dataStore.data.map { pref ->
pref[stringPreferencesKey(key)] ?: defaultValue
}
}

suspend fun Context.writeInt(key: String, value: Int) {
dataStore.edit { pref -> pref[intPreferencesKey(key)] = value }
}

fun Context.readInt(key: String, defaultValue: Int): Flow<Int> {
return dataStore.data.map { pref ->
pref[intPreferencesKey(key)] ?: defaultValue
}
}

suspend fun Context.writeDouble(key: String, value: Double) {
dataStore.edit { pref -> pref[doublePreferencesKey(key)] = value }
}

fun Context.readDouble(key: String, defaultValue: Double): Flow<Double> {
return dataStore.data.map { pref ->
pref[doublePreferencesKey(key)] ?: defaultValue
}
}

suspend fun Context.writeLong(key: String, value: Long) {
dataStore.edit { pref -> pref[longPreferencesKey(key)] = value }
}

fun Context.readLong(key: String, defaultValue: Long): Flow<Long> {
return dataStore.data.map { pref ->
pref[longPreferencesKey(key)] ?: defaultValue
}
}

suspend fun Context.writeBool(key: String, value: Boolean) {
dataStore.edit { pref -> pref[booleanPreferencesKey(key)] = value }
}

fun Context.readBool(key: String, defaultValue: Boolean): Flow<Boolean> {
return dataStore.data.map { pref ->
pref[booleanPreferencesKey(key)] ?: defaultValue
}
}

➤ What annotations are there in Hilt

@HiltAndroidApp: An annotation that must be added to the application class in order to initialize Hilt in the application.

@AndroidEntryPoint: An annotation that must be added to Android components such as Activity, Fragment, Service and Broadcast Receiver to enable automatic dependency injection and their lifecycle management.

@HiltViewModel: annotation for ViewModel

@Inject: An annotation that must be added to constructors, fields, or methods that require dependency injection.
Example:

class SomeClass @Inject constructor(
val someOtherClass: SOmeOtherClass,
) { }

@Module: An annotation that must be added to the class that provides the dependencies.
Priver:

@Module
@InstallIn(SingletonComponent::class)
internal class ApiModule {

@Provides
@Singleton
internal fun provideDefaultGson(): Gson {
return GsonBuilder()
.create()
}
}

@Provides: An annotation that must be added to a method inside a module to indicate what dependency the method provides.

@Singleton: An annotation that should be added to the Provides method to indicate that the dependency should only be created once and reused.

@Qualifier: An annotation that is used to identify a specific instance of a dependency when there are multiple instances of the same type.

@Named: An annotation that does the same thing as @Qualifier, but to specify the identifier as a string.

➤ What is the difference between Flow, StateFlow and SharedFlow in Kotlin

Flow, StateFlow and SharedFlow: These are Kotlin components that allow you to work with asynchronous data streams. Each of them has its own unique features and can be used depending on the needs of a specific task.

Flow: This is an asynchronous data flow that allows the processing of sets of values that can be processed as they arrive. Each value is processed independently of other values, allowing for parallel processing. Flow provides a wide range of operators for manipulating data, such as map, filter, zip, etc. Flow is an asynchronous stream that processes values as they arrive.

fun getData(): Flow<List<String>> = flow {
delay(1000)
val data = listOf("value1", "value2", "value3")
emit(data)
}

lifecycleScope.launch {
// subscribe to the stream
getData().collect { data ->
// processing the received values
for (value in data) {
Log.d("FlowExample", value)
}
}
}

StateFlow: This is a special type of Flow that allows you to store and process the current state of an application. StateFlow can be used to represent UI state, such as loading state, error state, etc. It is an observable thread that will automatically notify its observers (eg fragments, activities) about changes in the current state. When the current state changes, all observers will be notified of the new value.

class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value += 1
}
fun decrement() {
_count.value -= 1
}
}

class MainActivity : AppCompatActivity() {
private lateinit var viewModel: CounterViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
viewModel.count.onEach { count ->
countTextView.text = count.toString()
}.launchIn(lifecycleScope)
incrementButton.setOnClickListener {
viewModel.increment()
}
decrementButton.setOnClickListener {
viewModel.decrement()
}
}
}

SharedFlow: This is also a special type of Flow that allows multiple subscribers to receive the same value. Thus, SharedFlow can be used to create an observable flow that can be subscribed to by multiple observers. When a new value is received, it will automatically notify all subscribers of the new value.
As you can see, the main difference between Flow, StateFlow and SharedFlow is their specialization and purpose. Flow is a general-purpose tool for working with asynchronous data flows, StateFlow is for managing application state and notifying about state changes, and SharedFlow is for creating observable flows that can be subscribed to by multiple observers.

➤ How in Kotlin you can set parameters in xml for Custom View

class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

init {
setupAttributes(attrs)
}

private fun setupAttributes(attrs: AttributeSet?) {
context.withStyledAttributes(attrs, R.styleable.CustomView) {
getBoolean(R.styleable.CustomView_variable_name, true).let { variable ->

}
}
}
}

// write in attrs.xml
<declare-styleable name="CustomView">
<attr format="boolean" name="variable_name" />
</declare-styleable>

// now in the fragment layout we can add
<com._.CustomView
android:id="@+id/customView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:variable_name="true"
/>

// now you can see in the preview and in the application
// changes associated with this variable
// if this is needed only for preview purposes,
// use tools:variable_name="true" instead of app:variable_name="true"

Basic types of variables

<attr format="boolean" name="name">
<attr format="string"name="name">
<attr format="integer"name="name">
<attr format="reference"name="name">
<attr format="color"name="name">
<attr format="reference|color"name="name">
<attr format="dimension"name="name">

<attr format="enum" name="gravity">
<enum name="start" value="0" />
<enum name="end" value="1" />
</attr>

➤ What is Zygote in Android

In the Android operating system, the Zygote process (from English “zygote”) is a special process that starts at system startup and pre-initializes, compiles and caches application code to speed up their launch. When starting a new application, Zygote creates a new process that inherits the precompiled code from Zygote, which also helps speed up application startup and reduce memory consumption. Zygote also provides system services and resources that can be used by applications, such as system widgets, location services, security certificates, etc. Zygote plays an important role in the Android experience by allowing apps to launch faster and using system resources more efficiently.

➤ How to download executable code from the Internet in java

To load executable code, the dynamic class loading mechanism is used. To do this, you can use the java.net.URLClassLoader class, which allows you to load classes from various sources, including files on the local file system and remote files via a URL.

import java.net.URL;
import java.net.URLClassLoader;

public class RemoteClassLoader {

public static void main(String[] args) throws Exception {
URL url = new URL("http://example.com/classes/MyClass.class");
URLClassLoader classLoader = new URLClassLoader(new URL[] { url });

// Loading a class from a remote source
Class<?> clazz = classLoader.loadClass("MyClass");

// Create an instance of the class
Object obj = clazz.newInstance();

// Method call
clazz.getMethod("someMethod").invoke(obj);
}
}

Here we create an instance of URLClassLoader with an array of URLs containing the path to the class to load. Then we load the class with the loadClass() method and create an instance of the class with the newInstance() method. You can then call methods of the loaded class using reflection.

➤ What formats of data exchange with the server exist

JSON (JavaScript Object Notation): was created as an alternative to more complex data interchange formats such as XML, and is designed to be easy to read and write by both humans and computers. The JSON format is a collection of key-value pairs, where the key is a string and the value can be any valid data type, including objects, arrays, numbers, strings, booleans, and null.

SOAP (Simple Object Access Protocol): based on XML (Extensible Markup Language) and defines the format of the message, as well as the rules for processing it.

Protobuf(Protocol Buffers): A binary format that is not human readable, unlike formats such as XML and JSON. Instead, it is a sequence of bytes that can only be parsed using a special tool. The protobuf protocol itself defines a data description language that is used to describe the structure of the data that will be transmitted over the network. This language has its own syntax that is used to describe fields and their data types. Each field in the data structure has a unique identifier and data type. Allows you to compactly and efficiently serialize data for transmission over the network, while ensuring high performance and compatibility between different versions of applications.

➤ What is EventBus and how it works

EventBus is an event bus that allows different components to send and receive events without being explicitly tied to each other. When a component sends an event to the EventBus, all components that are interested in that event can subscribe to the EventBus and be notified when the event occurs. This allows components to respond to events without having an explicit dependency on the event sender. There are several implementations of EventBus in Kotlin. One of the most popular is the EventBus library from GreenRobot.

data class MessageEvent(val message: String)

EventBus.getDefault().post(MessageEvent("Hello"))

class MySubscriber {
@Subscribe
fun onMessageEvent(event: MessageEvent) {
println("Message received: ${event.message}")
}
}

eventBus.register(MySubscriber())

➤ How Handler can be used in Anroid development

Handler for delaying code execution:

val handler = Handler()
handler.postDelayed({
// Code to be executed after a certain time
}, 2000) // Delay in milliseconds (in this case 2 seconds)

Handler for executing code on the main thread:

val handler = Handler(Looper.getMainLooper())
handler.post {
// Code to be executed on the main thread
}

Handler for executing code in a background thread:

val handlerThread = HandlerThread("HandlerThreadTag")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// Code to be executed in a background thread
}

Handler for executing code cyclically:

val handler = Handler()
handler.post(object : Runnable {
override fun run() {
// Code to be executed
handler.postDelayed(this, 1000) // Repeat every second
}
})

--

--