Einführung in Kotlin: Android-Programmierung für Menschen
Veröffentlicht: 2022-03-11In einer perfekten Android-Welt ist die Hauptsprache Java wirklich modern, klar und elegant. Sie können weniger schreiben, indem Sie mehr tun, und wenn eine neue Funktion erscheint, können Entwickler sie einfach verwenden, indem Sie die Version in Gradle erhöhen. Während Sie dann eine sehr schöne App erstellen, scheint sie vollständig testbar, erweiterbar und wartbar zu sein. Unsere Aktivitäten sind nicht zu umfangreich und kompliziert, wir können Datenquellen ohne große Unterschiede von Datenbank zu Web ändern und so weiter. Klingt toll, oder? Leider ist die Android-Welt nicht so ideal. Google strebt immer noch nach Perfektion, aber wir alle wissen, dass es keine idealen Welten gibt. Daher müssen wir uns bei dieser großen Reise in die Android-Welt selbst helfen.
Was ist Kotlin und warum sollten Sie es verwenden?
Also die Erstsprache. Ich denke, dass Java nicht der Meister der Eleganz oder Klarheit ist, und es ist weder modern noch ausdrucksstark (und ich schätze, Sie stimmen zu). Der Nachteil ist, dass wir unterhalb von Android N immer noch auf Java 6 (einschließlich einiger kleiner Teile von Java 7) beschränkt sind. Entwickler können RetroLambda auch anhängen, um Lambda-Ausdrücke in ihrem Code zu verwenden, was bei der Verwendung von RxJava sehr nützlich ist. Oberhalb von Android N können wir einige der neuen Funktionen von Java 8 nutzen, aber es ist immer noch das alte, schwere Java. Sehr oft höre ich Android-Entwickler sagen: „Ich wünschte, Android würde eine schönere Sprache unterstützen, wie es iOS mit Swift tut“. Und was wäre, wenn ich Ihnen sagen würde, dass Sie eine sehr schöne, einfache Sprache verwenden können, mit Nullsicherheit, Lambdas und vielen anderen netten neuen Funktionen? Willkommen bei Kotlin.
Was ist Kotlin?
Kotlin ist eine neue Sprache (manchmal auch als Swift für Android bezeichnet), die vom JetBrains-Team entwickelt wurde und jetzt in der Version 1.0.2 vorliegt. Was es in der Android-Entwicklung nützlich macht, ist, dass es in JVM-Bytecode kompiliert wird und auch in JavaScript kompiliert werden kann. Es ist voll kompatibel mit Java, und Kotlin-Code kann einfach in Java-Code konvertiert werden und umgekehrt (es gibt ein Plugin von JetBrains). Das bedeutet, dass Kotlin jedes in Java geschriebene Framework, jede Bibliothek usw. verwenden kann. Auf Android wird es von Gradle integriert. Wenn Sie eine vorhandene Android-App haben und eine neue Funktion in Kotlin implementieren möchten, ohne die gesamte App neu zu schreiben, beginnen Sie einfach mit dem Schreiben in Kotlin, und es wird funktionieren.
Aber was sind die „großartigen neuen Funktionen“? Lassen Sie mich einige auflisten:
Optionale und benannte Funktionsparameter
fun createDate(day: Int, month: Int, year: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) { print("TEST", "$day-$month-$year $hour:$minute:$second") }
Wir können die Methode createDate auf verschiedene Arten aufrufen
createDate(1,2,2016) prints: '1-2-2016 0:0:0' createDate(1,2,2016, 12) prints: '1-2-2016 12:0:0' createDate(1,2,2016, minute = 30) prints: '1-2-2016 0:30:0'
Null Sicherheit
Wenn eine Variable null sein kann, wird der Code nicht kompiliert, es sei denn, wir zwingen sie dazu. Der folgende Code wird einen Fehler haben – nullableVar kann null sein:
var nullableVar: String? = “”; nullableVar.length;
Zum Kompilieren müssen wir prüfen, ob es nicht null ist:
if(nullableVar){ nullableVar.length }
Oder kürzer:
nullableVar?.length
Auf diese Weise passiert nichts, wenn nullableVar null ist. Andernfalls können wir die Variable als nicht nullable markieren, ohne ein Fragezeichen nach dem Typ:
var nonNullableVar: String = “”; nonNullableVar.length;
Dieser Code wird kompiliert, und wenn wir nonNullableVar null zuweisen möchten, zeigt der Compiler einen Fehler an.
Es gibt auch einen sehr nützlichen Elvis-Operator:
var stringLength = nullableVar?.length ?: 0
Wenn dann nullableVar null ist (also nullableVar?.length null zurückgibt), hat stringLength den Wert 0.
Veränderliche und unveränderliche Variablen
Im obigen Beispiel verwende ich var beim Definieren einer Variablen. Dies ist änderbar, wir können es jederzeit neu zuweisen. Wenn wir möchten, dass diese Variable unveränderlich ist (in vielen Fällen sollten wir das tun), verwenden wir val (als Wert, nicht als Variable):
val immutable: Int = 1
Danach erlaubt uns der Compiler keine Neuzuweisung auf unveränderlich.
Lambdas
Wir alle wissen, was ein Lambda ist, also zeige ich hier nur, wie wir es in Kotlin verwenden können:
button.setOnClickListener({ view -> Log.d("Kotlin","Click")})
Oder wenn die Funktion das einzige oder letzte Argument ist:
button.setOnClickListener { Log.d("Kotlin","Click")}
Erweiterungen
Erweiterungen sind eine sehr hilfreiche Sprachfunktion, dank der wir bestehende Klassen „erweitern“ können, selbst wenn sie abgeschlossen sind oder wir keinen Zugriff auf ihren Quellcode haben.
Um beispielsweise einen Zeichenfolgenwert aus dem Bearbeitungstext zu erhalten, können wir die Funktion schreiben, anstatt jedes Mal editText.text.toString() zu schreiben:
fun EditText.textValue(): String{ return text.toString() }
Oder kürzer:
fun EditText.textValue() = text.toString()
Und jetzt, mit jeder Instanz von EditText:
editText.textValue()
Oder wir können eine Eigenschaft hinzufügen, die dasselbe zurückgibt:
var EditText.textValue: String get() = text.toString() set(v) {setText(v)}
Bedienerüberladung
Manchmal nützlich, wenn wir Objekte hinzufügen, multiplizieren oder vergleichen möchten. Kotlin ermöglicht das Überladen von binären Operatoren (plus, minus, plusAssign, range usw.), Array-Operatoren (get, set, get range, set range) sowie Gleichheits- und unären Operationen (+a, -a usw.)
Datenklasse
Wie viele Codezeilen benötigen Sie, um eine User-Klasse in Java mit drei Eigenschaften zu implementieren: copy, equals, hashCode und toString? In Kaotlin braucht man nur eine Zeile:
data class User(val name: String, val surname: String, val age: Int)
Diese Datenklasse bietet die Methoden equals(), hashCode() und copy() sowie toString(), das den Benutzer ausgibt als:
User(name=John, surname=Doe, age=23)
Datenklassen bieten auch einige andere nützliche Funktionen und Eigenschaften, die Sie in der Kotlin-Dokumentation sehen können.
Anko-Erweiterungen
Sie verwenden Butterknife oder Android-Erweiterungen, nicht wahr? Was ist, wenn Sie diese Bibliothek nicht einmal verwenden müssen, und nachdem Sie Ansichten in XML deklariert haben, verwenden Sie sie einfach aus dem Code anhand ihrer ID (wie bei XAML in C#):
<Button android: android:layout_width="match_parent" android:layout_height="wrap_content" />
loginBtn.setOnClickListener{}
Kotlin hat sehr nützliche Anko-Erweiterungen, und damit müssen Sie Ihrer Aktivität nicht sagen, was loginBtn ist, es weiß es einfach durch „Importieren“ von XML:
import kotlinx.android.synthetic.main.activity_main.*
Es gibt viele andere nützliche Dinge in Anko, darunter das Starten von Aktivitäten, das Zeigen von Toasts und so weiter. Dies ist nicht das Hauptziel von Anko - es wurde entwickelt, um Layouts einfach aus Code zu erstellen. Wenn Sie also ein Layout programmgesteuert erstellen müssen, ist dies der beste Weg.
Dies ist nur ein kurzer Blick auf Kotlin. Ich empfehle, den Blog von Antonio Leiva und sein Buch „Kotlin für Android-Entwickler“ und natürlich die offizielle Kotlin-Website zu lesen.
Was ist MVP und warum?
Eine schöne, kraftvolle und klare Sprache reicht nicht aus. Es ist sehr einfach, ohne gute Architektur in jeder Sprache chaotische Apps zu schreiben. Android-Entwickler (hauptsächlich Anfänger, aber auch Fortgeschrittene) übertragen Activity häufig die Verantwortung für alles um sie herum. Aktivität (oder Fragment oder andere Ansicht) lädt Daten herunter, sendet sie zum Speichern, präsentiert sie, reagiert auf Benutzerinteraktionen, bearbeitet Daten, verwaltet alle untergeordneten Ansichten . . . und oft noch viel mehr. Das ist zu viel für so instabile Objekte wie Aktivitäten oder Fragmente (es reicht, den Bildschirm zu drehen, und Aktivität sagt „Auf Wiedersehen….“).
Eine sehr gute Idee ist es, Verantwortlichkeiten von Ansichten zu trennen und sie so dumm wie möglich zu machen. Ansichten (Aktivitäten, Fragmente, benutzerdefinierte Ansichten oder was auch immer Daten auf dem Bildschirm darstellt) sollten nur für die Verwaltung ihrer Unteransichten verantwortlich sein. Ansichten sollten Moderatoren haben, die mit dem Modell kommunizieren und ihnen sagen, was sie tun sollen. Dies ist kurz gesagt das Model-View-Presenter-Muster (für mich sollte es Model-Presenter-View heißen, um Verbindungen zwischen Schichten anzuzeigen).
„Hey, so etwas kenne ich, und es heißt MVC!“ - hast du nicht gedacht? Nein, MVP ist nicht dasselbe wie MVC. Im MVC-Muster kann Ihre Ansicht mit dem Modell kommunizieren. Während Sie MVP verwenden, lassen Sie keine Kommunikation zwischen diesen beiden Schichten zu – View kann nur über Presenter mit Model kommunizieren. Das einzige, was View über Model weiß, kann die Datenstruktur sein. View weiß beispielsweise, wie Benutzer angezeigt werden, aber nicht wann. Hier ist ein einfaches Beispiel:
View weiß: „Ich bin Activity, ich habe zwei EditTexts und einen Button. Wenn jemand auf die Schaltfläche klickt, sollte ich es meinem Präsentator mitteilen und ihm die Werte von EditTexts übergeben. Und das ist alles, ich kann schlafen, bis der nächste Klick oder der Moderator mir sagt, was ich tun soll.“
Der Präsentator weiß, dass sich irgendwo eine Ansicht befindet, und er weiß, welche Operationen diese Ansicht ausführen kann. Er weiß auch, dass er, wenn er zwei Zeichenfolgen erhält, einen Benutzer aus diesen beiden Zeichenfolgen erstellen und Daten zum Speichern an das Modell senden sollte, und wenn das Speichern erfolgreich ist, der Ansicht „Erfolgsinfo anzeigen“ mitteilen sollte.
Model weiß nur, wo sich Daten befinden, wo sie gespeichert werden sollen und welche Operationen mit den Daten durchgeführt werden sollen.
In MVP geschriebene Anwendungen lassen sich einfach testen, warten und wiederverwenden. Ein reiner Moderator sollte nichts über die Android-Plattform wissen. Es sollte eine reine Java-Klasse (oder in unserem Fall Kotlin) sein. Dadurch können wir unseren Presenter in anderen Projekten wiederverwenden. Wir können auch problemlos Unit-Tests schreiben und Model, View und Presenter separat testen.
Ein kleiner Exkurs: MVP sollte ein Teil von Uncle Bobs Clean Architecture sein, um Anwendungen noch flexibler und schöner zu gestalten. Ich werde versuchen, das nächste Mal darüber zu schreiben.
Beispiel-App mit MVP und Kotlin
Das ist genug Theorie, sehen wir uns etwas Code an! Okay, versuchen wir, eine einfache App zu erstellen. Das Hauptziel dieser App ist es, Benutzer zu erstellen. Der erste Bildschirm hat zwei EditTexts (Name und Nachname) und eine Schaltfläche (Speichern). Nachdem Sie Vor- und Nachnamen eingegeben und auf „Speichern“ geklickt haben, sollte die App „Benutzer ist gespeichert“ anzeigen und zum nächsten Bildschirm wechseln, auf dem gespeicherte Vor- und Nachnamen angezeigt werden. Wenn der Name oder Nachname leer ist, sollte die App den Benutzer nicht speichern und einen Fehler anzeigen, der angibt, was falsch ist.
Das erste, was Sie nach dem Erstellen eines Android Studio-Projekts tun müssen, ist die Konfiguration von Kotlin. Sie sollten das Kotlin-Plugin installieren, und nach dem Neustart können Sie unter Extras > Kotlin auf „Kotlin im Projekt konfigurieren“ klicken. Die IDE fügt Gradle Kotlin-Abhängigkeiten hinzu. Wenn Sie vorhandenen Code haben, können Sie ihn ganz einfach in Kotlin konvertieren (Strg+Umschalt+Alt+K oder Code > Java-Datei in Kotlin konvertieren). Wenn etwas nicht stimmt und das Projekt nicht kompiliert wird oder Gradle Kotlin nicht sieht, können Sie den Code der auf GitHub verfügbaren App überprüfen.
Nachdem wir nun ein Projekt haben, beginnen wir mit der Erstellung unserer ersten Ansicht – CreateUserView. Diese Ansicht sollte die zuvor erwähnten Funktionalitäten haben, damit wir dafür eine Schnittstelle schreiben können:

interface CreateUserView : View { fun showEmptyNameError() /* show error when name is empty */ fun showEmptySurnameError() /* show error when surname is empty */ fun showUserSaved() /* show user saved info */ fun showUserDetails(user: User) /* show user details */ }
Wie Sie sehen können, ähnelt Kotlin Java beim Deklarieren von Funktionen. All dies sind Funktionen, die nichts zurückgeben, und die letzten haben einen Parameter. Das ist der Unterschied, der Parametertyp kommt nach dem Namen. Die View-Oberfläche stammt nicht von Android – es ist unsere einfache, leere Oberfläche:
interface View
Die Schnittstelle von Basic Presenter sollte eine Eigenschaft vom Typ View und mindestens eine Methode (z. B. onDestroy) haben, wobei diese Eigenschaft auf null gesetzt wird:
interface Presenter<T : View> { var view: T? fun onDestroy(){ view = null } }
Hier sehen Sie ein weiteres Kotlin-Feature – Sie können Eigenschaften in Schnittstellen deklarieren und dort auch Methoden implementieren.
Unser CreateUserView muss mit CreateUserPresenter kommunizieren. Die einzige zusätzliche Funktion, die dieser Presenter benötigt, ist saveUser mit zwei String-Argumenten:
interface CreateUserPresenter<T : View>: Presenter<T> { fun saveUser(name: String, surname: String) }
Wir brauchen auch die Modelldefinition - es ist die frühere Datenklasse erwähnt:
data class User(val name: String, val surname: String)
Nachdem alle Schnittstellen deklariert sind, können wir mit der Implementierung beginnen.
CreateUserPresenter wird in CreateUserPresenterImpl implementiert:
class CreateUserPresenterImpl(override var view: CreateUserView?): CreateUserPresenter<CreateUserView> { override fun saveUser(name: String, surname: String) { } }
Die erste Zeile mit Klassendefinition:
CreateUserPresenterImpl(override var view: CreateUserView?)
Ist ein Konstruktor, verwenden wir ihn zum Zuweisen von View-Eigenschaften, die in der Schnittstelle definiert sind.
MainActivity, unsere CreateUserView-Implementierung, benötigt einen Verweis auf CreateUserPresenter:
class MainActivity : AppCompatActivity(), CreateUserView { private val presenter: CreateUserPresenter<CreateUserView> by lazy { CreateUserPresenterImpl(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) saveUserBtn.setOnClickListener{ presenter.saveUser(userName.textValue(), userSurname.textValue()) /*use of textValue() extension, mentioned earlier */ } } override fun showEmptyNameError() { userName.error = getString(R.string.name_empty_error) /* it's equal to userName.setError() - Kotlin allows us to use property */ } override fun showEmptySurnameError() { userSurname.error = getString(R.string.surname_empty_error) } override fun showUserSaved() { toast(R.string.user_saved) /* anko extension - equal to Toast.makeText(this, R.string.user_saved, Toast.LENGTH_LONG) */ } override fun showUserDetails(user: User) { } override fun onDestroy() { presenter.onDestroy() } }
Zu Beginn der Klasse haben wir unseren Präsentator definiert:
private val presenter: CreateUserPresenter<CreateUserView> by lazy { CreateUserPresenterImpl(this) }
Es ist als unveränderlich (val) definiert und wird von einem faulen Delegaten erstellt, der beim ersten Mal zugewiesen wird, wenn es benötigt wird. Außerdem sind wir sicher, dass es nicht null sein wird (kein Fragezeichen nach der Definition).
Wenn der Benutzer auf die Schaltfläche Speichern klickt, sendet View Informationen mit EditTexts-Werten an Presenter. Wenn das passiert, sollte der Benutzer gespeichert werden, also müssen wir die Methode saveUser in Presenter (und einige der Funktionen des Modells) implementieren:
override fun saveUser(name: String, surname: String) { val user = User(name, surname) when(UserValidator.validateUser(user)){ UserError.EMPTY_NAME -> view?.showEmptyNameError() UserError.EMPTY_SURNAME -> view?.showEmptySurnameError() UserError.NO_ERROR -> { UserStore.saveUser(user) view?.showUserSaved() view?.showUserDetails(user) } } }
Wenn ein Benutzer erstellt wird, wird er an UserValidator gesendet, um die Gültigkeit zu prüfen. Dann wird gemäß dem Ergebnis der Validierung ein geeignetes Verfahren aufgerufen. Das when() {}-Konstrukt ist dasselbe wie switch/case in Java. Aber es ist leistungsfähiger – Kotlin erlaubt nicht nur die Verwendung von enum oder int in „case“, sondern auch von Ranges, Strings oder Objekttypen. Es muss alle Möglichkeiten enthalten oder einen else-Ausdruck haben. Hier deckt es alle UserError-Werte ab.
Durch die Verwendung von view?.showEmptyNameError() (mit einem Fragezeichen nach view) sind wir vor NullPointer geschützt. Die Ansicht kann in der onDestroy-Methode auf Null gesetzt werden, und mit dieser Konstruktion wird nichts passieren.
Wenn ein Benutzermodell keine Fehler aufweist, weist es UserStore an, es zu speichern, und weist dann View an, Erfolg und Details anzuzeigen.
Wie bereits erwähnt, müssen wir einige Modelldinge implementieren:
enum class UserError { EMPTY_NAME, EMPTY_SURNAME, NO_ERROR } object UserStore { fun saveUser(user: User){ //Save user somewhere: Database, SharedPreferences, send to web... } } object UserValidator { fun validateUser(user: User): UserError { with(user){ if(name.isNullOrEmpty()) return UserError.EMPTY_NAME if(surname.isNullOrEmpty()) return UserError.EMPTY_SURNAME } return UserError.NO_ERROR } }
Das Interessanteste hier ist UserValidator. Durch die Verwendung des Objektworts können wir eine Singleton-Klasse erstellen, ohne uns Gedanken über Threads, private Konstruktoren usw. machen zu müssen.
Als nächstes - in der Methode validateUser(user) gibt es den Ausdruck with(user) {}. Code innerhalb eines solchen Blocks wird im Kontext des Objekts ausgeführt, übergeben mit Name und Nachname sind die Eigenschaften des Benutzers.
Dazu kommt noch eine Kleinigkeit. Der gesamte obige Code, von Enum bis UserValidator, Definition wird in einer Datei platziert (Definition der Benutzerklasse ist auch hier). Kotlin zwingt Sie nicht, jede öffentliche Klasse in einer einzigen Datei zu haben (oder Klasse genau als Datei zu benennen). Wenn Sie also einige kurze Stücke verwandten Codes haben (Datenklassen, Erweiterungen, Funktionen, Konstanten – Kotlin benötigt keine Klasse für Funktion oder Konstante), können Sie ihn in einer einzigen Datei platzieren, anstatt ihn auf alle Dateien im Projekt zu verteilen.
Wenn ein Benutzer gespeichert ist, sollte unsere App das anzeigen. Wir brauchen eine andere Ansicht – es kann jede Android-Ansicht, benutzerdefinierte Ansicht, Fragment oder Aktivität sein. Ich habe Aktivität gewählt.
Lassen Sie uns also die UserDetailsView-Schnittstelle definieren. Es kann den Benutzer anzeigen, sollte aber auch einen Fehler anzeigen, wenn der Benutzer nicht vorhanden ist:
interface UserDetailsView { fun showUserDetails(user: User) fun showNoUserError() }
Als nächstes UserDetailsPresenter. Es sollte eine Benutzereigenschaft haben:
interface UserDetailsPresenter<T: View>: Presenter<T> { var user: User? }
Diese Schnittstelle wird in UserDetailsPresenterImpl implementiert. Es muss die Benutzereigenschaft überschreiben. Jedes Mal, wenn diese Eigenschaft zugewiesen wird, sollte der Benutzer die Ansicht aktualisieren. Dafür können wir einen Eigenschaftssetzer verwenden:
class UserDetailsPresenterImpl(override var view: UserDetailsView?): UserDetailsPresenter<UserDetailsView> { override var user: User? = null set(value) { field = value if(field != null){ view?.showUserDetails(field!!) } else { view?.showNoUserError() } } }
Die Implementierung von UserDetailsView, UserDetailsActivity, ist sehr einfach. Wie zuvor haben wir ein Presenter-Objekt, das durch Lazy Loading erstellt wurde. Der anzuzeigende Benutzer sollte über die Absicht übergeben werden. Im Moment gibt es ein kleines Problem damit, und wir werden es gleich lösen. Wenn wir einen Benutzer aus der Absicht haben, muss View ihn seinem Moderator zuweisen. Danach wird der Benutzer auf dem Bildschirm aktualisiert oder, wenn er null ist, wird der Fehler angezeigt (und die Aktivität wird beendet – aber der Moderator weiß nichts davon):
class UserDetailsActivity: AppCompatActivity(), UserDetailsView { private val presenter: UserDetailsPresenter<UserDetailsView> by lazy { UserDetailsPresenterImpl(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_details) val user = intent.getParcelableExtra<User>(USER_KEY) presenter.user = user } override fun showUserDetails(user: User) { userFullName.text = "${user.name} ${user.surname}" } override fun showNoUserError() { toast(R.string.no_user_error) finish() } override fun onDestroy() { presenter.onDestroy() } }
Das Übergeben von Objekten über Absichten erfordert, dass dieses Objekt die Parcelable-Schnittstelle implementiert. Das ist sehr „schmutzige“ Arbeit. Ich persönlich hasse es wegen all der CREATORs, Eigenschaften, Speichern, Wiederherstellen und so weiter. Glücklicherweise gibt es das richtige Plugin, Parcelable for Kotlin. Nach der Installation können wir Parcelable mit nur einem Klick generieren.
Als letztes müssen wir showUserDetails(user: User) in unserer MainActivity implementieren:
override fun showUserDetails(user: User) { startActivity<UserDetailsActivity>(USER_KEY to user) /* anko extension - starts UserDetailsActivity and pass user as USER_KEY in intent */ }
Und das ist alles.
Wir haben eine einfache App, die einen Benutzer speichert (tatsächlich wird er nicht gespeichert, aber wir können diese Funktionalität hinzufügen, ohne Moderator oder Ansicht zu berühren) und ihn auf dem Bildschirm präsentiert. Wenn wir in Zukunft die Darstellung des Benutzers auf dem Bildschirm ändern möchten, z. B. von zwei Aktivitäten zu zwei Fragmenten in einer Aktivität oder zwei benutzerdefinierten Ansichten, werden Änderungen nur in Ansichtsklassen vorgenommen. Natürlich, wenn wir die Funktionalität oder die Struktur des Modells nicht ändern. Presenter, der nicht genau weiß, was View ist, braucht keine Änderungen.
Was kommt als nächstes?
In unserer App wird Presenter jedes Mal erstellt, wenn eine Aktivität erstellt wird. Dieser Ansatz oder sein Gegenteil, wenn Presenter über Aktivitätsinstanzen hinweg bestehen bleiben soll, ist Gegenstand zahlreicher Diskussionen im Internet. Für mich hängt es von der App, ihren Bedürfnissen und dem Entwickler ab. Manchmal ist es besser, den Moderator zu zerstören, manchmal nicht. Wenn Sie sich entscheiden, einen zu behalten, ist es eine sehr interessante Technik, LoaderManager dafür zu verwenden.
Wie bereits erwähnt, sollte MVP Teil der Clean-Architektur von Uncle Bob sein. Darüber hinaus sollten gute Entwickler Dagger verwenden, um Presenter-Abhängigkeiten in Aktivitäten einzufügen. Es hilft auch, Code in Zukunft zu warten, zu testen und wiederzuverwenden. Derzeit funktioniert Kotlin sehr gut mit Dagger (vor der offiziellen Veröffentlichung war es nicht so einfach) und auch mit anderen hilfreichen Android-Bibliotheken.
Einpacken
Für mich ist Kotlin eine großartige Sprache. Es ist modern, klar und ausdrucksstark, während es immer noch von großartigen Menschen entwickelt wird. Und wir können jede neue Version auf jedem Android-Gerät und jeder Version verwenden. Was auch immer mich auf Java wütend macht, Kotlin verbessert sich.
Natürlich ist, wie gesagt, nichts ideal. Kotlin hat auch einige Nachteile. Die neuesten Gradle-Plugin-Versionen (hauptsächlich von Alpha oder Beta) funktionieren nicht gut mit dieser Sprache. Viele Leute beschweren sich, dass die Bauzeit etwas länger ist als bei reinem Java, und APKs einige zusätzliche MB haben. Aber Android Studio und Gradle verbessern sich immer noch, und Telefone haben immer mehr Platz für Apps. Deshalb glaube ich, dass Kotlin eine sehr schöne Sprache für jeden Android-Entwickler sein kann. Probieren Sie es einfach aus und teilen Sie in den Kommentaren unten Ihre Meinung mit.
Der Quellcode der Beispiel-App ist auf Github verfügbar: github.com/tomaszczura/AndroidMVPKotlin