Weiter geht’s mit Teil 6 der Kotlin-Updates-Serie. Diesmal gehe ich auf die Neuerungen ein, die sich mit Android Studio 4.2 ergeben. Den größten Änderungsbedarf verursacht das Gradle-Plugin kotlin-android-extensions
, das jetzt deprecated ist.
Dieser Text bezieht sich auf die folgenden Versionsnummern:
Android Studio: 4.2.2
Kotlin: 1.5.21
Gradle: 6.7
Neue Projekte
Wenn Sie in Android Studio 4.2.2 mit allen Update ein neues Projekt einrichten, kommen Kotlin 1.5.21 und Gradle 6.7 zum Einsatz. build.gradle
(Projekt) enthält zwar weiterhin den Verweis auf das jcenter
-Repository, aber auch den Hinweis, dass diese Paketquelle demnächst nicht mehr aktiv sein wird. Am besten kommentieren Sie die Quelle aus.
Bei meinen Experimenten trat immer wieder die Fehlermeldung Path.op() not supported auf. Offenbar muss in Android Studio die neue Rendering Engine aktiviert werden: Settings/Experimental/Use new Layout Rendering Engine.
Beachten Sie, dass Android Studio 4.2 merkwürdigerweise selbst bei neuen Projekten das »View Binding« (siehe unten) nicht automatisch aktiviert und Sie diesbezüglich im Regen stehen lässt. Merkwürdig!
Sie müssen die unten beschriebenen Aktionen auch in neuen Projekten durchführen. Das gilt insbesondere für die Datei build.gradle
(Module), in die Sie buildFeatures { viewBinding true }
einfügen müssen, als auch für MainActivity.kt
, wo Sie die Variable binding
deklarieren und den Code in onCreate
entsprechend ändern müssen.
Vorhandene Projekte aktualisieren
Um zu testen, wie einfach die Aktualisierung älterer Projekte gelingt, habe ich in den Gradle-Dateien des Währungsumrechner (siehe Kapitel 25 im Kotlin-Buch) sämtliche Versionsnummern auf den neuesten Stand aktualisiert. Offen gesagt war ich auf Kompatibilitätsprobleme eingestellt, aber überraschenderweise funktionierte der Code auch nach dem Umbau fehlerfrei.
kotlin-android-extensions is deprecated
So weit, so gut! Allerdings liefert das Build-System eine neue Warnung:
The kotlin-android-extensions Gradle plugin is deprecated. Please use this migration guide (https://goo.gle/kotlin-android-extensions-deprecation) to start working with View Binding (https://developer.android.com/topic/libraries/view-binding) and the kotlin-parcelize plugin.
Vorerst können Sie diese Warnung ignorieren. Auch wenn das Gradle-Plugin deprecated ist — noch funktioniert es ja. Ich habe die Vermutung, dass dies aufgrund unzähliger Projekte, die davon abhängig sind, noch eine Weile so bleiben wird. Aber längerfristig ist die Verwendung von deprecated-Komponenten selten eine gute Idee. Und bei neuen Projekten fehlt das Plugin ohnedies. Es wird Ihnen also nicht erspart bleiben, sich an neue Arbeitstechniken zu gewöhnen.
Welche Funktion hatte nun diese kotlin-android-extension
? Dieses Gradle-Plugin stellte in der Vergangenheit sicher, dass Sie im Code unmittelbar auf Steuerelemente zugreifen konnten. Wenn Sie also ein TextView-Steuerelement mit mytext
benannten (id-Eigenschaft), dann konnten Sie in der Folge im Code mit mytext.text = "abc"
den dort angezeigten Text anzeigen. import kotlinx.android.synthetic.main.fragment_about.view.*
und vom Plugin erzeugter synthetischer Code stellten sicher, dass der Steuerelementzugriff wie von Zauberhand funktionierte.
Die nunmehr empfohlene Vorgehensweise sieht wie folgt aus: Zuerst bauen Sie in build.gradle
auf Modulebene die folgenden Zeilen ein. (Merkwürdigerweise fehlt dieser Code auch bei neu eingerichteten Projekten.) Es ist keine Referenz auf neue Bibliotheken oder Plugins erforderlich.
// Datei build.gradle (Module)
android {
...
// die folgenden drei Zeilen hinzufügen, um den neuen
// View-Binding-Mechanismus zu aktivieren
buildFeatures {
viewBinding true
}
}
In der Klassendatei für die Aktivität brauchen Sie eine neue binding
-Variable. Deren Datentyp setzt sich aus dem Namen der Layout-Datei aus. Wenn es also bei einem Minimalprojekt die Datei activity_main.xml
gibt, hat die resultierende Binding-Klasse den Namen ActivtyMainBinding
.
Diese Variable initialisieren Sie in onCreate
. In der Folge können Sie dann auf alle Steuerelemente in der Form binding.<name>
zugreifen. Der Code zum Hello-World-Projekt aus Kapitel 21, wo nach dem Klick auf einen Button in einem Textfeld Datum und Uhrzeit angezeigt werden, sieht damit so aus:
// Datei MainActivity.kt
...
import info.kofler.test_as_42.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
// layout/activity_main.xml -> ActivityMainBinding usw.
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Zugriff auf Steuerelemente via binding
binding = ActivityMainBinding.inflate(layoutInflater)
// den folgenden Code ausführen, wenn 'mybutton' angeklickt wird
binding.mybutton.setOnClickListener {
val loc = Locale.getDefault()
val fmt = DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG, loc)
binding.mytext.text = "Datum und Uhrzeit:\n" + fmt.format(Date())
}
setContentView(binding.root)
}
}
Die zu ändernden Code-Passagen finden Sie am schnellsten, wenn Sie die Anweisung import kotlinx.android.synthetic.main.activity_main.*
auskommentieren. Alle Zugriffe auf Steuerelemente ohne binding
werden dann als Fehler markiert.
Für Fragmente gilt an sich die gleiche Vorgehensweise. Allerdings ist der Zugriff auf binding
erst ab dem Aufruf von onCreateView()
und nur bis zum Aufruf von onDestroyView()
erlaubt. Die Dokumentation empfiehlt deswegen, binding
als Property zu implementieren und eine zusätzliche Variable _binding
einzuführen. Der Code sieht dann so aus:
class MyFragment : Fragment(), CoroutineScope by MainScope() {
// layout/fragment_myname.xml -> FragmentMynameBinding usw.
private var _binding: FragmentMynameBinding? = null
// This property is only valid between onCreateView and
// onDestroyView!
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View?
{
_binding = FragmentAboutBinding.inflate(inflater, container, false)
// Zugriff auf Steuerelemente via binding
binding.mytext.text = "abc"
binding.mybutton.setOnClickListener { ... }
return binding.root
}
}
Ein wenig diffiziler ist die Umstellung von Adapter- und ViewHolder-Klassen zur Darstellung von Listen. Das folgende, stark gekürzte Listing zeigt die prinzipielle Vorgehensweise anhand für das Layout item_currency.xml
und die zugehörige Code-Datei CurrencyAdapter.kt
des Währungsumrechners (Kapitel 25):
// an die ViewHolder-Klasse binding statt view übergeben
class CurrencyViewHolder(val binding: ItemCurrencyBinding)
: RecyclerView.ViewHolder(binding.root)
{
val txtCurrency : TextView = binding.txtCurrency
val imgFlag : ImageView = binding.imgFlag
}
// in der Adapter-Klasse in onCreateViewHolder mit Bindings arbeiten
class CurrencyAdapter(private val cc: CurrencyCalculator,
private var selectedCurrency: MutableLiveData<String>,
private val context: Context)
: RecyclerView.Adapter<CurrencyViewHolder>()
{
...
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): CurrencyViewHolder
{
// layout/item_currency.xml -> ItemCurrencyBinding usw.
val binding = ItemCurrencyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.setOnClickListener {
selectedCurrency.value = binding.txtCurrency.text.toString()
notifyDataSetChanged() // alles neu zeichnen
}
return CurrencyViewHolder(binding)
}
}
Um ein vorhandenes Projekt auf die neue Binding-Technologie umzustellen, bauen Sie zuerst in build.gradle
die Anweisung viewBinding true
ein. Dann bauen Sie eine Activity- bzw. Fragment-Klasse nach der anderen um. (Ein Mischbetrieb zwischen der veralteten Kotlin-Android-Extension und Bindings ist erlaubt.) Zuletzt entfernen Sie die kotlin-android-extensions
-Zeile aus build.gradle
.
Ich habe den Währungsumrechner (Kapitel 25) innerhalb einer halben Stunde entsprechend umgestellt. Wenn man den neuen Mechanismus einmal verstanden hat, ist das keine Hexerei. Dennoch ist es natürlich ein mühsamer Prozess, und es ist ärgerlich, dass es für derartige Arbeiten keinen Assistenten gibt. Xcode bietet diesbezüglich viel mehr Komfort als Android Studio.
build.gradle (Module)
Zum Abschluss als Referenz noch die vollständige Datei build.gradle
für das Modul des Währungsumrechners:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlinx-serialization'
// id 'kotlin-android-extensions' -> ersetzt durch View Binding
}
android {
compileSdkVersion 30
defaultConfig {
applicationId "info.kofler.currencyconverter"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
// Koroutinen
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'
implementation 'com.google.code.gson:gson:2.8.6'
// Serialisierungs-Runtime
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.2.1"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
}
Beachten Sie, dass die in der Vergangenheit übliche Zeile implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
nicht mehr erforderlich ist. Das Build-System kümmert sich selbst um den Import der Kotlin-Standardbibliothek in der richtigen Version.
Updates/Links
- https://developer.android.com/studio/releases
- https://goo.gle/kotlin-android-extensions-deprecation
- https://developer.android.com/topic/libraries/view-binding
- https://medium.com/androiddevelopers/use-view-binding-to-replace-findviewbyid-c83942471fc
- https://stackoverflow.com/questions/59809654/how-to-solve-render-problem-path-op-not-supported
- https://stackoverflow.com/questions/49956051/warning-kotlin-plugin-version-is-not-the-same-as-library-version-but-it-is
Download
Die aktualisierten und auf View-Bindung umgestellten Beispielprojekte der Kapitel 21 bis 25 können Sie hier herunterladen:
https://kofler.info/uploads/kotlin/kap21-25-mit-view-binding.zip
Ich habe auch den Evaluation-Client aus Kapitel 30 auf View-Binding umgestellt. Die Projekte finden Sie im ktor-Update-Artikel zum Download.
Die Kotlin-Updates-Serie
Weitere Kotlin-Update-Artikel finden Sie hier auf meiner Website: