Well, I've recently started to catch up with Kotlin programming language, which replaces Java as main Android development language.
This post will be a first in the series - in this one I'll simply "translate" some Java code to Kotlin, in future ones I'll elaborate more on Kotlin's syntax (making them something like tutorial, I hope).
I've created a simple (one activity) app - in Java at first - which takes the text from an EditText, saves it to internal data, allows to read it and allows to add it as a note to the notification area. There are also some unnecessary (in this context) things, like a constant - it will be our excuse to show some Kotlin's syntax.
In the beginning, let's create a simple layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="160dp"
android:layout_marginBottom="32dp"
android:onClick="onAddNotificationButtonClick"
android:text="Add notification"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.486"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.171" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="392dp"
android:onClick="onSaveClick"
android:text="Save"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.281"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="392dp"
android:onClick="onLoadClick"
android:text="Load"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.758"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="76dp"
android:onClick="onButtonOpenClick"
android:text="Open"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<EditText
android:id="@+id/editTextTextMultiLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="90dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="111dp"
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button4" />
</androidx.constraintlayout.widget.ConstraintLayout>
... and save it as activity_main.xml in the res/layout .
In layout editor it looks like this:
It has a button, which adds a notification to the tray, then a multiline edit-text allowing us to input the notification's content, a textView which I forgot to use ;), and then three buttons:
- the Save button causes textView content to be saved in internal storage (persistantly)
- the Load button reads data from internal storage and shows them in the textView
- the Open button opens another Activity... I'll exlain in a moment.
Let's begin with creating the typical Android Activity class and initializing some textView and multiline edit text. To be specific - I'll create two separate activities, but they're be exactly the same (even using the same layout). One will be in Java, another one - in Kotlin. The Open button in each of them will open the other one: Java -> Kotlin -> Java...
JAVA:
public class MainActivity extends AppCompatActivity {
TextView tv;
EditText editTextTextMultiline;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.textView);
editTextTextMultiline = findViewById(R.id.editTextTextMultiLine);
}
KOTLIN:
class SecondActivity : AppCompatActivity() {
// the default visibility modifier is public:
private lateinit var tv: TextView;
private lateinit var editTextMultiline: EditText;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv = findViewById(R.id.textView);
editTextMultiline = findViewById(R.id.editTextTextMultiLine)
}
Ok, so let's go:
- in Kotlin, the default visibility modifier is public (package-private in Java). A bit strange if you're used to most of the object-oriented languages, but doesn't seem to be a problem. BTW Android Studio marks the functions it 'thinks' could be private and allows us to change the visibility automatically.
- the variable declaration is done using the keyword var.
- you can't just declare variable without initialing it OR stating explicitly it's to be initialized later by using the lateinit keyword.
- the type declaration syntax is bit funny, looks Pascal-like to me.
- override becomes modifier instead of annotation
- the nullable parameter has to be expliitly stated as nullable (Bundle?, not Bundle type)
- semicolons after instructions are optional (unnecessary, in fact, I'll try to avoid them later)
- function declaration looks different (more about this in a moment)
Ok, now let's declare a variable, a constant, a simple function and a function called by the button in activity (chosen in a layout xml with onClick):
Java:
int someNumber = 0;
final float PI = 3.1415f;
int getIncrementedNumber(int number){
return number+1;
}public void onChangeTextClick(View v) {
someNumber = getIncrementedNumber(someNumber);
tv.setText(String.format(Locale.getDefault(), "%d", someNumber));
}
Kotlin:
var someNumber = 0;
val PI = 3.1415f;
private fun getIncrementedNumber(number: Int): Int{
return number+1
}fun onChangeTextClick(v: View) {
someNumber = getIncrementedNumber(someNumber)
tv.text = String.format(Locale.getDefault(), "%d", someNumber)
}
- constant is declared using val keyword
- function is declared using fun keyword, the returned type for function is added after as a last part of its prototype (: Int{ part). Analogous to variable declaration. If function does not return anythong (void-like), the returned type part is omitted.
- getters and setters are refered to like field names, not like methods (no parenthesis).
Overall, code in Kotlin seems to be a bit more concise.
Now let's do the most basic thing in Android: open another activity. The Kotlin one will open Java activity, the Java activity will open Kotlin one :)
Java:
public void onButtonOpenClick(View v) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
Kotlin:
fun onButtonOpenClick(v: View) {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
In Kotlin:
- using val instead of explicit class name save us some space
- constructor is called without the new keyword
- reference to class looks a bit different.
Now something longer. Let's create a method for creating a notification channel. It will work just on Android 8.0+ for now (the system changed then):
Java:
private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "chanName";
String description = "chanDesc";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(
"someChannelId432", name, importance);
channel.setDescription(description);
NotificationManager notificationManager =
getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
Kotlin:
private fun createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "chanName"
val description = "chanDesc"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(
"someChannelId4234", name, importance)
channel.description = description
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
Creating notificationManager requires casting - to be honest, I don't know why.
Method for saving some string to file (internal storage):
Java:
void saveDataToFile(String filename, String fileContents) {
try {
FileOutputStream fos =
this.getBaseContext().openFileOutput(filename,
Context.MODE_PRIVATE);
fos.write(fileContents.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
Kotlin:
private fun saveDataToFile(fileName: String, fileContents: String) {
try {
val fos: FileOutputStream =
this.baseContext.openFileOutput(fileName, Context.MODE_PRIVATE)
fos.write(fileContents.toByteArray())
} catch (e: IOException) {
e.printStackTrace()
}
}
(please mind the exception type declaration syntax in catch)
... and for reading from file:
Java:
String readFromFile(String filename) {
FileInputStream fis;
StringBuilder stringBuilder = new StringBuilder();
try {
fis = MainActivity.this.openFileInput(filename);
InputStreamReader isr =
new InputStreamReader(fis, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
while (line != null && line!="") {
stringBuilder.append(line);
line = reader.readLine();
if(line!=null)
stringBuilder.append('\n');
}
} catch (IOException e) {
// Error occurred when opening raw file for reading.
}
return stringBuilder.toString();
}
Kotlin:
private fun readFromFile(fileName: String): String {
val fis: FileInputStream?
val stringBuilder = StringBuilder()
try {
fis = (this@SecondActivity).openFileInput(fileName)
val isr = InputStreamReader(fis, StandardCharsets.UTF_8)
val reader = BufferedReader(isr)
var line = reader.readLine()
while (line != null && line != "") {
stringBuilder.append(line)
line = reader.readLine()
if(line!=null)
stringBuilder.append('\n')
}
} catch (e: IOException) {
// Error occurred when opening raw file for reading.
}
return stringBuilder.toString()
}
FileInputStream? has to explicitly declared with ? as nullable. if, while and return statements are similar to Java.
Now let's call our methods:
public void onSaveClick(View v) {
saveDataToFile(someFileName, editTextTextMultiline.
getText().toString());
}
public void onLoadClick(View v) {
editTextTextMultiline.
setText(readFromFile(someFileName));
}
becomes:
fun onSaveClick(v: View) {
saveDataToFile(someFileName, editTextMultiline.text.toString())
}
fun onLoadClick(v: View) {
editTextMultiline.setText(readFromFile(someFileName))
}
Finally, let's create the method for adding notification, called when "Add notification" button is pressed.
Java:
public void onAddNotificationButtonClick(View v) {
createNotificationChannel();
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this,
"someChannelId432")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("My notification")
.setContentText("")
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(editTextTextMultiline.getText()))
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(this);
notificationManager.notify(4234, builder.build());
}
} // class end
...and Kotlin:
fun onAddNotificationButtonClick(v: View) {
createNotificationChannel()
val builder = NotificationCompat.Builder(this,
"someChannelId432")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("My kotlinofication")
.setContentText("")
.setStyle(NotificationCompat.BigTextStyle().
bigText(editTextMultiline.text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.notify(3213, builder.build())
}
} // class end
Again: it's similar. Lack of explicit types declarations (NotificationCompat.Builder builder becomes val builder) no new keyword and lack of semicolons makes the code a bit shorter. Quite readable, in my opinion.
One last thing: manifest, without it updated the second activity couldn't be open. It should look like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplicationjava">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplicationJava">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity">
</activity>
</application>
</manifest>
That's it for now. In the future posts I'll elaborate more on details, providing more examples of short Java code and corresponsing Kotlin code.
No comments:
Post a Comment