Friday, April 26, 2024

Android java: using SQLiteOpenHelper instead Room Library

Using additional Room may increase size of application, means more lines required to executed. I preferred to implement SQLiteOpenHelper rather then using Room Library.

Here is code for extending SQLiteOpenHelper:

package com.dedetok.radiowalkman.mydb;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.Nullable;

/*
 * added 20240319
 * Async task will be done in DB Repository to access database (RUD)
 */

public class MyDBHelper extends SQLiteOpenHelper {

    final static String dbName = "myradiolist"; // db name must final static
    final static int dbversion = 2; // db version must final static

    public MyDBHelper(@Nullable Context appContext) {
        //
        super(appContext, dbName, null, dbversion);
    }

    /*
     * Only called once after creating a new database
     * Will not called if database exist (upgrade version)
     */
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        //Log.e("dedetok","onCreate"); //debug
        createMyDB(sqLiteDatabase);
    }

    /*
     * Only called once if database exist and version number increase (upgrade)
     */
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        // upgrade from 1 to 2
        //Log.e("dedetok","onUpgrade "+oldVersion+" to "+newVersion); //debug
        if (oldVersion==1) {
            upgradeDBV1toV2(sqLiteDatabase);
        }

    }

    /*
     * version 1
     */
    private void createMyDB(SQLiteDatabase sqLiteDatabase) {
        //Log.e("dedetok","createMyDB"); //debug
        // Main table radiolist
        String sqlCreateRadioList = "CREATE TABLE IF NOT EXISTS "+
                "radiolist ("+
                "countryname TEXT NOT NULL,"+
                "cityname TEXT NOT NULL,"+
                "radiostation TEXT NOT NULL,"+
                "logourl TEXT NOT NULL,"+
                "streamurl TEXT NOT NULL,"+
                "PRIMARY KEY(countryname, cityname, radiostation));";
        sqLiteDatabase.execSQL(sqlCreateRadioList);
        // table radiolistversion only has 1 record and one column
        String sqlCreateRadioListVersion = "CREATE TABLE IF NOT EXISTS "+
                "radiolistversion ("+
                "listversion TEXT NOT NULL,"+
                "PRIMARY KEY(listversion));";
        sqLiteDatabase.execSQL(sqlCreateRadioListVersion);

        //Log.e("dedetok", "create table app_preferences"); //debug
        // 20240422
        upgradeDBV1toV2(sqLiteDatabase);

    }

    /*
     * version 1 to version 2
     */
    private void upgradeDBV1toV2(SQLiteDatabase sqLiteDatabase) {
        //Log.e("dedetok", "upgradeDBV1toV2"); //debug
        // 20240422
        String sqlCreatePreferences = "CREATE TABLE IF NOT EXISTS "+
                "app_preferences ("+
                "pref_key TEXT NOT NULL,"+
                "pref_value TEXT NOT NULL,"+
                "PRIMARY KEY(pref_key));";
        sqLiteDatabase.execSQL(sqlCreatePreferences);
    }

}

 This is java class in Radio Walkman application.

Thursday, April 18, 2024

Java 17 JList using ListModel and JComboBox using BasicComboBoxRenderer to wrap data (hide id)

Source code: 

com.dedetok.tutorialcustomjlist.Person.java

package com.dedetok.tutorialcustomjlist;

public class Person {
    int id;
    String name;
    
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

com.dedetok.tutorialcustomjlist.MyListModel.java

package com.dedetok.tutorialcustomjlist;

import java.util.Vector;
import javax.swing.ListModel;
import javax.swing.event.ListDataListener;

public class MyListModel implements ListModel<String> {
    Vector<Person> myData = new Vector<>();

    public MyListModel(Vector<Person> myData) {
        this.myData = myData;
    }
    
    @Override
    public int getSize() {
        return myData.size();
    }

    // to render in JList
    @Override
    public String getElementAt(int index) {
        Person tmpP = myData.get(index);
        return tmpP.name;
    }

    @Override
    public void addListDataListener(ListDataListener l) {
        // TODO Auto-generated method stub
       
    }

    @Override
    public void removeListDataListener(ListDataListener l) {
        // TODO Auto-generated method stub
       
    }

}

com.dedetok.tutorialcustomjlist.MyCBRenderer.java

package com.dedetok.tutorialcustomjlist;

import java.awt.Component;

import javax.swing.JList;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

public class MyCBRenderer extends BasicComboBoxRenderer {

    // to render to JComboBox
    @Override
    public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected,
            boolean cellHasFocus) {
        // TODO Auto-generated method stub
        super.getListCellRendererComponent(list, value, index, isSelected,
            cellHasFocus);
        Person mPerson = (Person) value;
        setText(mPerson.name);
        return this;
    }
}

com.dedetok.tutorialcustomjlist.TutorialJList.java

package com.dedetok.tutorialcustomjlist;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.SpringLayout;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.JComboBox;

public class TutorialJList {

    private JFrame frame;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TutorialJList window = new TutorialJList();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public TutorialJList() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        SpringLayout springLayout = new SpringLayout();
        frame.getContentPane().setLayout(springLayout);
       
        Vector<Person> myList = new Vector<>();
        Person tmp = new Person(1,"one");
        myList.add(tmp);
        tmp = new Person(2,"two");
        myList.add(tmp);
        tmp = new Person(3,"Three");
        myList.add(tmp);
       
        MyListModel myDataList = new MyListModel(myList); // implements ListModel<String>
       
        JList<String> list = new JList<>(myDataList);
        list.addListSelectionListener(new ListSelectionListener( ) {

            @Override
            public void valueChanged(ListSelectionEvent e) {
                // TODO Auto-generated method stub
                if (!e.getValueIsAdjusting()) {
                    int selectInt = list.getSelectedIndex();
                    Person tmpP = myDataList.myData.get(selectInt);
                    System.out.println(tmpP.id+" "+tmpP.name);
                }
            }
           
        });


        springLayout.putConstraint(SpringLayout.NORTH, list, 0, SpringLayout.NORTH, frame.getContentPane());
        springLayout.putConstraint(SpringLayout.WEST, list, 0, SpringLayout.WEST, frame.getContentPane());
        springLayout.putConstraint(SpringLayout.EAST, list, 436, SpringLayout.WEST, frame.getContentPane());
        frame.getContentPane().add(list);

        JComboBox<Person> comboBox = new JComboBox<>(myList);
        MyCBRenderer myCBRenderer = new MyCBRenderer();
        comboBox.setRenderer(myCBRenderer);
        ActionListener myAL = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                Person myPerson = (Person) comboBox.getSelectedItem();
                System.out.println(myPerson.id+" "+myPerson.name);
            }
           
        };
        comboBox.addActionListener(myAL);

        springLayout.putConstraint(SpringLayout.WEST, comboBox, 0, SpringLayout.WEST, list);
        springLayout.putConstraint(SpringLayout.SOUTH, comboBox, 0, SpringLayout.SOUTH, frame.getContentPane());
        frame.getContentPane().add(comboBox);
       
    }
}


Monday, April 1, 2024

Android java source code com.dedetok.turuntirta

This is source code for com.dedetok.turuntirta and com.dedetok.bramara. I keep using legacy MediaPlayer.

Put turuntirta.mp3 in folder [Workspace]/Wargasari/app/src/main/res/raw/

build.gradle.kts (Module: app)

plugins {
    id("com.android.application")
}

android {
    namespace = "com.dedetok.turuntirta"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.dedetok.turuntirta"
        minSdk = 24
        targetSdk = 34
        versionCode = 202402
        versionName = "2024.02"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    implementation("com.google.android.gms:play-services-ads-lite:23.0.0")
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- For apps targeting Android 13 or higher & GMA SDK version 20.3.0 or lower -->
    <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <!-- permission for media player -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <!-- android:enableOnBackInvokedCallback="true" Back Pressed Target API level>33 -->
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/my_ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/my_ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TurunTirta"
        tools:targetApi="31"
        android:enableOnBackInvokedCallback="true"
        >
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:configChanges="orientation|screenSize"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
        <!-- AD App ID: ca-app-pub-0220748109202708~7061470683 : AndroidManifest.xml -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-0220748109202708~7061470683"
            />
    </application>

</manifest>

res -> values -> strings.xml

<resources>
    <string name="app_name" translatable="false">Kidung Turun Tirta</string>

    <string-array name="kidung">
        <item>Turun tirta saking luhur|\nMenyiratan pemangkune|\nMekalangan muncrat mumbul|\nMapan tirtha merta jati|\nPaican Bhatara sami|\nPanglukatan dasa mala|\nSami pada lebur|\nMalene ring gumi||</item>
        <item>Meketis ping tiga sampun|\nPabahan Siwa dwarane|\nWasuhane raris kinum|\nPing tiga lantas mesugi|\nRing waktra megentos genti|\nToya amertha Hyang Widhine|\nSami sampun puput|\nMengalangin hati||</item>
    </string-array>

    <array name="my_font_size">
        <item>18</item>
        <item>24</item>
        <item>32</item>
        <item>42</item>
    </array>
    <string name="dialog_title">Exit Application</string>
    <string name="dialog_message">Do you want to exit application?</string>
    <string name="play_pause">Play/Pause</string>
    <string name="zoom_in">Zoom In</string>
    <string name="zoom_out">Zoom Out</string>
    <string name="bait">Stanza</string>
    <string name="play_repeat">Repeat play</string>

</resources>

res -> values -> ic_launcher_background.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="ic_launcher_background">#FFFFFF</color>
</resources>

res -> Layout -> layout_stanza.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >

    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/stanza_title"
        android:textColor="#9C27B0"
        />
    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/stanza_content"
        />

</androidx.appcompat.widget.LinearLayoutCompat>

res -> Layout -> activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
    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"
    android:orientation="vertical"
    >

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >

        <androidx.appcompat.widget.AppCompatButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/b_zoom_in"
            android:text="@string/zoom_in"
            />
        <androidx.appcompat.widget.AppCompatButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/b_zoom_out"
            android:text="@string/zoom_out"
            />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_weight="1"
        />

    <androidx.appcompat.widget.AppCompatSeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/my_seekbar"
        />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="center">
        <androidx.appcompat.widget.AppCompatImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/my_play"
            android:src="@android:drawable/ic_media_play"
            android:layout_gravity="center_horizontal"
            android:contentDescription="@string/play_pause"
            />
        <androidx.appcompat.widget.SwitchCompat
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/switch_repeat"
            android:layout_gravity="center_horizontal"
            android:contentDescription="@string/play_repeat"
            android:text="@string/play_repeat"
            />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <!-- Sample AdMob unit ID: ca-app-pub-3940256099942544/6300978111 -->
    <!-- AD Unit ID: ca-app-pub-0220748109202708/8538203883 : layout -->
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:adSize="BANNER"
        android:layout_gravity="end|center"
        app:adUnitId="ca-app-pub-0220748109202708/8538203883"
        />
</androidx.appcompat.widget.LinearLayoutCompat>

java -> StanzaContainer.java

package package com.dedetok.wargasari;;

public class StanzaContainer {

    public String stanzaTitle;
    public String stanzaContent;

    public StanzaContainer(String stanzaTitle, String stanzaContent) {
        this.stanzaTitle = stanzaTitle;
        this.stanzaContent = stanzaContent;

    }
}

java -> MyAdapter.java

package com.dedetok.wargasari;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter  {

    ArrayList<StanzaContainer>  myStanza;

    int myUnitTextSize;
    float myTextSize;

    public MyAdapter(ArrayList<StanzaContainer> myStanza, int unit, float size) {
        this.myStanza = myStanza;
        this.myUnitTextSize = unit;
        this.myTextSize = size;

    }

    @NonNull
    @Override
    public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).
            inflate(R.layout.layout_stanza, parent, false);
        return new MyAdapter.MyHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        StanzaContainer myTmpStanza = myStanza.get(position);
        ((MyHolder) holder).stanzaTitle.setText(myTmpStanza.stanzaTitle);
        ((MyHolder) holder).stanzaTitle.setTextSize(myUnitTextSize, myTextSize);
        ((MyHolder) holder).stanzaContent.setText(myTmpStanza.stanzaContent);
        ((MyHolder) holder).stanzaContent.setTextSize(myUnitTextSize, myTextSize);
    }

    @Override
    public int getItemCount() {
        return myStanza.size();
    }

    public void changeFontSize(int unit, float size) {
        this.myUnitTextSize = unit;
        this.myTextSize = size;
        notifyDataSetChanged();
    }

    private class MyHolder extends RecyclerView.ViewHolder {

        public AppCompatTextView stanzaTitle, stanzaContent;

        public MyHolder(@NonNull View itemView) {
            super(itemView);

            stanzaTitle = itemView.findViewById(R.id.stanza_title);
            stanzaContent = itemView.findViewById(R.id.stanza_content);
        }
    }
}

java -> MainActivity.java

package com.dedetok.turuntirta;

import static android.util.TypedValue.COMPLEX_UNIT_SP;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatSeekBar;
import androidx.appcompat.widget.SwitchCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    MediaPlayer myMediaPlayer = null;
    AppCompatSeekBar mySeekBar;
    int cTextSizeState = 0;
    TypedArray availFontSize;
    float defaultTextSize = 18;
    boolean myPlayerStatus = false;
    private Bundle myBundle;
    private int myCurrentPos =0;
    static final String STATE_MY_PLAYER_STATUS = "currentStatus";
    static final String STATE_MY_PLAYER_POSITION = "currentPosition";
    AppCompatImageButton myPlayButton; //20240302
    // handler to update seekbar
    final Handler myHandler = new Handler(Looper.getMainLooper()); // new Handler() deprecated 20240302

    //20240423
    PowerManager.WakeLock wakeLock;

    /*
     * ## activity life cycle 1 ##
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // add version number from gradle into activity title
        try {
            PackageInfo myPackage = getPackageManager().getPackageInfo(getPackageName(),0);
            String myTitle =  getString(R.string.app_name)+" "+myPackage.versionName;
            setTitle(myTitle);
        } catch (PackageManager.NameNotFoundException e) {
            // Do Nothing, pass process
        }

        // ads:adUnitId ca-app-pub-0220748109202708/8538203883 : AdView in Layout
        // app id ca-app-pub-0220748109202708~7061470683    : AndroidManifest.xml
        AdView mAdView = findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().build();
        mAdView.loadAd(adRequest);

        // Button Play 20240302
        myPlayButton = findViewById(R.id.my_play);
        myPlayButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (myPlayerStatus) {
                    // pause
                    pauseMedia();
                } else {
                    // play
                    playMedia();
                }
            }
        });

        // seekbar
        mySeekBar = findViewById(R.id.my_seekbar);
        mySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int myProgress, boolean isFromUser) {

                if (isFromUser) {
                    if (myMediaPlayer!=null) {
                        myMediaPlayer.seekTo(myProgress);
                    }
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                // DO NOTHING
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                // DO NOTHING
            }
        });

        // recyclerview
        availFontSize = getResources().obtainTypedArray(R.array.my_font_size);
        RecyclerView myRecyclerView = findViewById(R.id.my_recycler_view);
        LinearLayoutManager myLinearLayout = new LinearLayoutManager(getApplicationContext());
        myLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
        myRecyclerView.setLayoutManager(myLinearLayout);
        MyAdapter myAdapter = new MyAdapter(getTexts(), COMPLEX_UNIT_SP, availFontSize.getFloat(cTextSizeState, defaultTextSize));
        myRecyclerView.setAdapter(myAdapter);

        // button zoom text
        AppCompatButton bZoomOut = findViewById(R.id.b_zoom_out);
        bZoomOut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (cTextSizeState==0) {
                    //Log.i("Font Change", "Zoom out do nothing");
                }  else {
                    cTextSizeState--;
                    myAdapter.changeFontSize(COMPLEX_UNIT_SP,availFontSize.getFloat(cTextSizeState, defaultTextSize));
                    //Log.i("Font Change", Float.toString(availFontSize.getFloat(cTextSizeState,defaultTextSize)));
                }
            }
        });
        AppCompatButton bZoomIn = findViewById(R.id.b_zoom_in);
        bZoomIn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (cTextSizeState+1==availFontSize.length()) {
                    //Log.i("Font Change", "Zoom in do nothing");
                }  else {
                    cTextSizeState++;
                    myAdapter.changeFontSize(COMPLEX_UNIT_SP,availFontSize.getFloat(cTextSizeState, defaultTextSize));
                    //Log.i("Font Change", Float.toString(availFontSize.getFloat(cTextSizeState,defaultTextSize)));
                }
            }
        });

        // to save state if user switch to / from other application
        myBundle = savedInstanceState; // added multimedia

        // onBackPressed() deprecated -> OnBackPressedCallback & getOnBackPressedDispatcher()
        // dispatcher
        OnBackPressedDispatcher myOnbackPressedDispatcher = getOnBackPressedDispatcher();
        // callback on back key pressed
        OnBackPressedCallback myBackPressedCallback = new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                // Log.e("dedetok", "handleOnBackPressed()"); //debug
                AlertDialog.Builder myAlertDialog = new AlertDialog.Builder(
                        MainActivity.this);
                myAlertDialog.setTitle(R.string.dialog_title).
                        setMessage(R.string.dialog_message).
                        setIcon(android.R.drawable.ic_dialog_alert).
                        setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                // finish() will execute onDestroy()
                                finish();
                            }
                        }).
                        setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                // do nothing
                            }
                        }).
                        show();

            }
        };
        // add callback to dispatcher
        myOnbackPressedDispatcher.addCallback(this, myBackPressedCallback);

        //20240422
        SwitchCompat sRepeat = findViewById(R.id.switch_repeat);
        sRepeat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean booleanRepeat) {
                //myDataProvider.setRepeat(booleanRepeat);
                if (myMediaPlayer!=null) {
                    myMediaPlayer.setLooping(booleanRepeat);
                }
            }
        });

    }

    // ## activity life cycle 2 ##
    // onStart() 20240302
    @Override
    protected void onStart() {
        super.onStart();
        // 20240423
        // Android N (7.0) or latter MediaPlayer should create in OnStart
        // Media Player
        // preparing mediaplayer
        // no need to call prepare(); create() does that for you
        if (myMediaPlayer==null) {
            myMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.turuntirta);
        }
        myMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                // seekbar at start and stop thread
                myPlayerStatus = mediaPlayer.isPlaying();
                myCurrentPos=0;
                mySeekBar.setProgress(myCurrentPos);
                if (!myMediaPlayer.isLooping()) {
                    pauseMedia();
                }
            }
        });
    }

    /*
     * ## activity life cycle 3 ## 20240302
     *    activity process 3
     *    onResume()
     *    @Override onRestoreInstanceState(Bundle savedInstanceState) ?
     */
    @Override
    public void onResume() {
        super.onResume();

        loadMyState();
        // link mySeekbar to myPlayer
        mySeekBar.setMax(myMediaPlayer.getDuration());
        if (myPlayerStatus && !myMediaPlayer.isPlaying()) {
            playMedia();
        }
        if (myMediaPlayer.isPlaying()) {
            myMediaPlayer.seekTo(myCurrentPos);
        }
    }

    /*
     * ## activity life cycle 4 ## 20240302
     * onPause()
     * @Override onSaveInstanceState(Bundle outState) ?
     */
    @Override
    public void onPause() {
        super.onPause();
    }

    // ## activity life cycle 5 ## 20240302
    @Override
    protected void onStop() {
        saveMyState();

        // 20240213
        // Android N (7.0) or latter MediaPlayer should stop and release in OnStop
        // we meed to keep play media even if screen does not on top
        // move release resource to onDestroy()
        super.onStop();

    }

    /*
     * ## activity life cycle 5 ## 20240302
     * clean up onDestroy()
     */
    @Override
    public void onDestroy() {
        //Log.e("dedetok","onDestroy Activity"); //debug

        // 20240423
        // release all media resources
        // release lock and executor include in funcion pauseMedia()
        pauseMedia();
        if (myMediaPlayer != null) {
            myMediaPlayer.release();
            myMediaPlayer = null;
        }

        super.onDestroy();

    }

    /* 20240302
     * onConfigurationChanged Called:
     * - after orientation change
     */
    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // DO NOTHING
    }

    /* 20240302
     * onSaveInstanceState Called:
     * - after switch to other application
     * - after onConfigurationChanged
     */
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        //Log.e("dedetok", "onSaveInstanceState"); // debug
        // TODO save something
    }

    /* 20240302
     * onRestoreInstanceState Called:
     * - after back to application
     * - after onConfigurationChanged
     */
    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        //Log.e("dedetok", "onRestoreInstanceState"); // debug
        // TODO save something

    }

    private ArrayList<StanzaContainer> getTexts() {
        String[] kidung = getResources().getStringArray(R.array.kidung);
        int numKidung = kidung.length;
        ArrayList<StanzaContainer> myStanza = new ArrayList<StanzaContainer>();
        for (int i=0;i<numKidung; i++) {
            int j=i+1;
            String stanzaTitle = getString(R.string.bait)+" "+j;
            StanzaContainer myTmpStanza = new StanzaContainer(stanzaTitle, kidung[i]);
            myStanza.add(myTmpStanza);
        }
        //Log.i("array",Integer.toString(kidunglist.length));
        return myStanza;
    }

    /*
     * to load state
     */
    void loadMyState() {
        if (myBundle != null) {
            super.onRestoreInstanceState(myBundle);
            // Save the user's current media player state
            myCurrentPos = myBundle.getInt(STATE_MY_PLAYER_POSITION);
            myPlayerStatus = myBundle.getBoolean(STATE_MY_PLAYER_STATUS);
        }
    }


    /*
     * to save state
     */
    void saveMyState() {
        if (myBundle != null) {
            myBundle.putInt(STATE_MY_PLAYER_POSITION, myCurrentPos);
            myBundle.putBoolean(STATE_MY_PLAYER_STATUS, myPlayerStatus);
            super.onSaveInstanceState(myBundle);
        }
    }

    // 20240302
    void playMedia() {
        //Log.e("dedetok", "playMedia create execcutor"); // debugg
        executor = Executors.newSingleThreadExecutor();

        //Log.e("dedetok", "playMedia wakelock and start"); // debugg

        // 20240423
        // try to get WAKE_LOCK
        if (wakeLock==null) {
            PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    "MyApp::com.dedetok.turuntirta");
        }
        wakeLock.acquire();
        myMediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
        myMediaPlayer.start();

        // set icon to pause
        myPlayButton.setImageResource(android.R.drawable.ic_media_pause);
        myPlayerStatus = myMediaPlayer.isPlaying();

        //Log.e("dedetok", "playMedia seekbar"); // debugg

        // update seekbar TODO CRASH
        executor.submit(mySeekbarUpdater);
    }

    // 20240302
    void pauseMedia() {
        //Log.e("dedetok", "pauseMedia pause"); // debugg
        myMediaPlayer.pause();
        // set icon to play
        myPlayButton.setImageResource(android.R.drawable.ic_media_play);
        myPlayerStatus = myMediaPlayer.isPlaying();

        //Log.e("dedetok", "pauseMedia waklock release"); // debugg

        // 20240423
        // try to release WAKE_LOCK
        if (wakeLock!=null) {
            wakeLock.release();
        }
        //Log.e("dedetok", "pauseMedia executor release"); // debugg
        if (executor!=null) {
            executor.shutdown();
        }
    }

    /* 20240302
     * Thread to update seekbar during mediaplayer run
     */
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Runnable mySeekbarUpdater = new Runnable() {
        @Override
        public void run() {
            if (myMediaPlayer!=null) {
                if (myMediaPlayer.isPlaying()) {
                    myCurrentPos = myMediaPlayer.getCurrentPosition();
                    mySeekBar.setProgress(myCurrentPos);
                    myHandler.postDelayed(this, 500); // 500 millisecond
                }
            }
        }
    };
}


Thursday, March 21, 2024

windows 11 x64: installing MinGW-w64 for Windows binary

  1. Download from GCC 32.2.0 with Posix thread without LLVM/Clang/LLD/LLDB: 7-Zip archive from  https://winlibs.com/ 
  2. Extract them in location easy to access e.q D:\mingw64
  3. Set your windows environment add mingw bin folder, System -> Advanced system settings -> Environment Variables -> System Variables -> Path -> Edit. Add mingw bin folder e.q. D:\mingw64\bin
  4. Save new configuration and mingw64 ready to use

Monday, March 11, 2024

Android java: develop android tv application

Add dependency build.gradle.kts

...
dependencies {
    implementation(libs.material)
    implementation("androidx.leanback:leanback:1.2.0-alpha04")
    // leanback-preference is an add-on that provides a settings UI for TV apps.
    implementation("androidx.leanback:leanback-preference:1.2.0-alpha04")
}
...

Add requirement in to AndroidManifest.xml

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <!-- Leanback MUST -->
    <uses-feature android:name="android.software.leanback" android:required="false" />
    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />

    <!-- MUST android:icon="@mipmap/ic_launcher" -->
    <!-- MUST android:banner="@drawable/mybannerandroidtv" 320x180 -->
    <application
        android:icon="@mipmap/ic_launcher"
        android:banner="@drawable/mybannerandroidtv"
...
        <activity
            android:name=".TV.MainActivity"
            android:exported="true"
            android:theme="@style/Theme.Leanback"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
            </intent-filter>
        </activity>
...

If you use Java, do not create activity from TV -> Empty Activity. It will use kotlin. Just create empty layout and java for main activity.

res -> layout -> fragment_tv_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"/>
</LinearLayout>

com.dedetok.tutorialandroidtv.TV.FragmentTVMain

package com.dedetok.tutorialandroidtv.TV;

import android.os.Bundle;

import androidx.fragment.app.Fragment;
import androidx.leanback.app.BrowseSupportFragment;

import com.dedetok.tutorialandroidtv.R;

//public class FragmentTVMain extends Fragment {
public class FragmentTVMain extends BrowseSupportFragment {

    /*
    // extends Fragment
    public FragmentTVMain() {
        super(R.layout.fragment_tv_main);
    }
     */

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }
}

activity_main_tv.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <!-- Screen elements that can render outside the overscan safe area go here -->

    <!-- Nested RelativeLayout with overscan-safe margin -->
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="27dp"
        android:layout_marginBottom="27dp"
        android:layout_marginLeft="48dp"
        android:layout_marginRight="48dp">

        <!-- Screen elements that need to be within the overscan safe area go here -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World" />
    </RelativeLayout>
</RelativeLayout>

com.dedetok.tutorialandroidtv.TV.MainActivity.java

package com.dedetok.tutorialandroidtv.TV;

import com.dedetok.tutorialandroidtv.R;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;

public class MainTVActivity extends FragmentActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tv_main);
    }

Note com.dedetok.tutorialandroidtv.TV.FragmentTVMain:

If you want to use fragment instead of BrowseSupportFragment, remove comment tag on fragment and add comment tag to BrowseSupportFragment 

Additional info:

to run your application on Android TV / Google TV

  1. enable developer mode your Android TV/GoogleTV
  2. in developer options enable usb debugging 
  3. in developer options enable internet adb / wireless adb (depend on system)
  4. run $ ./adb connect [your android tv/google tv ip], for example:
    $ ./adb connect 192.168.1.144
    connected to 192.168.1.144:5555
  5. your android tv/google tv will available on android studio your android studio


Tuesday, March 5, 2024

Android java: behaviour state when configuration change (rotating mobile phone) Android N (7.0 / API 24)

After rotating mobilephone

  1. onConfigurationChanged
  2. onPause
  3. onSaveInstanceState
  4. onStop
  5. onDestroy

Following

  1. onStart
  2. onRestoreInstanceState
  3. onResume

NOTE: Important to keep state UI

  • Save any state in onSaveInstanceState
  • Restore InstanceState  in onRestoreInstanceState

To restore UI from InstanceState:

  • restore in onResume
  • if you use background service in OnStart, wait until it is finish. Easier way is UI handler -> handleMessage  

Friday, March 1, 2024

Source code Kramaning (com.dedetok.kramaning)

build.gradle.kts (Module: app)

plugins {
    id("com.android.application")
}

android {
    namespace = "com.dedetok.kramaning"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.dedetok.kramaning"
        minSdk = 24
        targetSdk = 34
        versionCode = 202401
        versionName = "2024.01"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

}

dependencies {

    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    implementation ("com.google.android.gms:play-services-ads:22.6.0")

}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- For apps targeting Android 13 or higher & GMA SDK version 20.3.0 or lower -->
    <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
        <!-- AD App ID: ca-app-pub-0220748109202708~3978959687 -> AndroidManifest.xml -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-0220748109202708~3978959687"/>


    </application>

</manifest>

res -> values -> strings -> strings.xml

<resources>
    <string name="app_name">Kramaning</string>
    <string name="nav_open">Open</string>
    <string name="nav_close">Close</string>
    <string name="t_preparation">Preparation</string>
    <string name="t_trisandya">Trisandya</string>
    <string name="t_kramaning">Kramaning</string>
    <string name="t_metirta" translatable="false">Metirta &amp; Mebije</string>
</resources>

res -> values -> strings -> strings.xml (in)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Kramaning</string>
    <string name="nav_open">Buka</string>
    <string name="nav_close">Tutup</string>
    <string name="t_preparation">Persiapan</string>
    <string name="t_trisandya">Trisandya</string>
    <string name="t_kramaning">Kramaning</string>

</resources>

com.dedetok.kramaning.ScriptHindu.java

package com.dedetok.kramaning;

import java.util.ArrayList;

public class ScriptHindu {

    @SuppressWarnings("SpellCheckingInspection")
    private static final String[] textMantras = {
            "Om Ang Brahma Dupa Danta Ya Namah svaha||", // 0 dupa
            "Om Puspa Danta Ya Namah svaha||", // 1 flower
            "Om Waktra Sudayamam svaha||", // 2 mount
            "Om Sudayamam svaha, Om Ati Sudayamam svaha||", // 3 right hand
            "Om Prasada Stiti Sarira Suci Nirmalaya Namah svaha||", // 4 soul
            "Om Ang Namah, Om Ung Namah, Om Mang Namah||", // 5 pranayama
            "OM Bhur Bhubah Svah|\nTat savitur varenyam|\nBharga devasya dhimahi|\nDhiyo yo nah pracodayat||", // 6 traisandya 1
            "OM Narayana evedwam sarvam|\nYad butham Yac ca Bhavyam|\nNiskalanko niranjano nirvikalpo|\nNirakhyatah suddho deva eko|\nNarayana na dvitiyo asti kascit||", // 7 traisandya 2
            "OM Tvam siwah tvam mahadevah|\nIswarah paramesvarah|\nBrahma Visnuca rudrasca|\nPurusah parikirtitah||", // 8 traisandya 3
            "OM Papoham papokarmaham|\nPapatma papasambhavah|\nTrahi mam pundarikaksa|\nSabahyabyantarah sucih||", // 9 traisandya 4
            "OM Ksamasva mam mahadeva|\nSarvaprani hitangkara|\nMam moca sarva papebyah|\nPalayasva sada siva||", // 10 traisandya 5
            "OM Ksantavyah kayika dosah|\nKsantavyo vaciko mama|\nKsantavyo manasa dosah|\nTat pramadat ksamamsva mam||", // 11 traisandya 6
            "OM Canti Canti Canti||", // 12 traisandya 7
            "OM Atma tattvatma suddha ya mam svaha", // 13 kramaning 1
            "OM Adityah syah param jyoti|\nRakta teja namastute|\nSveta pangkaja madhyastha|\nBhaskara ya na namostute||\n\nOM Pranam ya bhaskaram dewam|\nSarwaklesa winasanam|\nParama ditya siwartam|\nBhukti mukti warapradam||\n\nOM Hrang hring sah parama ciwa raditya ya namah svaha||", // 14 kramaning 2
            "OM Namah deva ya adhista na ya|\nSarvavyapi nasiwaya|\nPadmasana eka pratisthaya|\nArdhanaresvar ya namah svaha||\n\nOM Brahma visnu isvara devam|\nJiwatmanan trilokanam|\nSarva jagat pratisthanam|\nSarva roga vinurcitam|\nSarva roga vinasamam|\nVigna desa vinasanam||\n\nOM Namah siva ya||", // 15 kramaning 3
            "OM Anugraha manoharam|\nDwva data nugrahakam|\nHyarcanam sarva pujanam|\nNamah sava nugrahakam||\n\nDeva devi maha sidhi|\nYajnangga nimalatmaka|\nLaksmi siddhisca dirgahayu|\nNirvigram suka vrdisca|\n\nOM Ggring anugahraha ya namah svaha|\nOM Gring anugraha ya namah svaha||\n\nOm Ayur verdi yaso verdi|\nVredi pradnya suka sriyam|\nDhrama santana verdisca|\nSantute sapta vrdayah||\n\nYata meru sthita devam|\nYawat gangga maha tale|\nCandrakau ganggane tawat|\nTattavatma wijayam bawat||\n\nOM Dhirgahayau tat astu svaha|\nOM Awignam astu tat astu svaha|\nOM Subahamastu tat astu svaha|\nOM Sukham bhavantu sruyam bhavantu purnam bhavantu||\n\nOm Sapta verdhi tat astu svaha|\nAntyestih parama pindam|\nAntyestih dewa mersittah|\nSwarvatih eka stnawa|\nSarwva dewa sukha pradadna|\nYa namo namah svaha||", // 16 kramaning 4
            "OM Maha deva devi suksma paramachintya ya nama svaha||", // 17 kramaning 5
            "Om ang brahma amrtha ya namah, Om ung wisnu amrtha ya namah, Om mang isvara amrtha ya namah||", // 18 tirta 1
            "Om Sarira paripurna ya namah, om ang ung mang sarira sudha, Pramantya ya namah, Om ung ksama sampurna ya namah||", // 19 tirta drink 2
            "Om Siva Amertha ya namah, om sadha siva amertha ya namah, Om parama siva amertha ya namah||", // 20 tirta wash face 3
            "Om çriyam bhawantu, Om sukam bhawantu, Om ksama sampurna ya namah svaha" // 21 Bije
    };

    private static final String[] titleMantrasID = {
            "Penyucian Dupa/Api", // 0 dupa
            "Penyucian Bunga", // 1 flower
            "Penyucian Mulut", // 2 mount
            "Penyucian Tangan", // 3 right hand
            "Penyucian Jiwa", // 4 soul
            "Pranayama", // 5 pranayama
            "Trisandya bait 1", // 6 traisandya 1
            "Trisandya bait 2", // 7 traisandya 2
            "Trisandya bait 3", // 8 traisandya 3
            "Trisandya bait 4", // 9 traisandya 4
            "Trisandya bait 5", // 10 traisandya 5
            "Trisandya bait 6", // 11 traisandya 6
            "Trisandya bait 7", // 12 traisandya 7
            "Kramaning bait 1", // 13 kramaning 1
            "Kramaning bait 2", // 14 kramaning 2
            "Kramaning bait 3", // 15 kramaning 3
            "Kramaning bait 4", // 16 kramaning 4
            "Kramaning bait 5", // 17 kramaning 5
            "Tirta dikepala", // 18 tirta 1
            "Tirta diminum", // 19 tirta drink 2
            "Tirta diraup", // 20 tirta wash face 3
            "Bije didahi, dtenggorokan &dimakan" // 21 Bije
    };

    private static final String[] titleMantrasEN = {
            "Penyucian Dupa/Api", // 0 dupa
            "Penyucian Bunga", // 1 flower
            "Penyucian Mulut", // 2 mount
            "Penyucian Tangan", // 3 right hand
            "Penyucian Jiwa", // 4 soul
            "Pranayama", // 5 pranayama
            "Trisandya bait 1", // 6 traisandya 1
            "Trisandya bait 2", // 7 traisandya 2
            "Trisandya bait 3", // 8 traisandya 3
            "Trisandya bait 4", // 9 traisandya 4
            "Trisandya bait 5", // 10 traisandya 5
            "Trisandya bait 6", // 11 traisandya 6
            "Trisandya bait 7", // 12 traisandya 7
            "Kramaning bait 1", // 13 kramaning 1
            "Kramaning bait 2", // 14 kramaning 2
            "Kramaning bait 3", // 15 kramaning 3
            "Kramaning bait 4", // 16 kramaning 4
            "Kramaning bait 5", // 17 kramaning 5
            "Tirta dikepala", // 18 tirta 1
            "Tirta diminum", // 19 tirta drink 2
            "Tirta diraup", // 20 tirta wash face 3
            "Bije didahi, dtenggorokan &dimakan" // 21 Bije
    };

    public static ArrayList<MantraContainer> getMantras(String myLang) {
        ArrayList<MantraContainer> tmpMantras= new ArrayList<MantraContainer>();
        for (int i=0;i<textMantras.length;i++) {
            MantraContainer tmpContainer = new MantraContainer();
            if (myLang.equalsIgnoreCase("id")) {
                tmpContainer.myTitle = titleMantrasID[i];
            } else {
                tmpContainer.myTitle = titleMantrasEN[i];
            }
            tmpContainer.myMantra = textMantras[i];
            tmpMantras.add(tmpContainer);
        }
        return tmpMantras;
    }
}

com.dedetok.kramaning.MantraContainer.java

package com.dedetok.kramaning;

public class MantraContainer {
    public String myTitle;
    public String myMantra;

}

com.dedetok.kramaning.MyAdapter.java

package com.dedetok.kramaning;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.appcompat.widget.AppCompatTextView;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter {

    private ArrayList<MantraContainer> myMantraAll;
    private ArrayList<MantraContainer> myMantraShow;

    public MyAdapter(String myLang) {
        super();
        this.myMantraAll = ScriptHindu.getMantras(myLang);
        this.myMantraShow = this.myMantraAll;

    }

    /*
     * mandatary extends RecyclerView.Adapter
     */
    @NonNull
    @Override
    public MyAdapter.MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View myView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.layout_viewitem, parent, false);
        return new MyAdapter.MyHolder (myView);
    }

    /*
     * mandatary extends RecyclerView.Adapter
     */
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        MantraContainer tmpMantraContainer = myMantraShow.get(position);
        ((MyHolder) holder).tvTitle.setText(tmpMantraContainer.myTitle);
        ((MyHolder) holder).tvMantra.setText(tmpMantraContainer.myMantra);
    }

    /*
     * mandatary extends RecyclerView.Adapter
     */
    @Override
    public int getItemCount() {
        return myMantraShow.size();
    }

    public void updateList(int myMin, int myMax) {
        myMantraShow = new ArrayList<MantraContainer>();
        for (int i=myMin;i<myMax;i++) {
            this.myMantraShow.add(this.myMantraAll.get(i));
        }
        notifyDataSetChanged();
    }

    private class MyHolder extends RecyclerView.ViewHolder {
        //public TextView tvTitle, tvMantra;
        public AppCompatTextView tvTitle, tvMantra;

        public MyHolder(@NonNull View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.tv_title);
            tvMantra = itemView.findViewById(R.id.tv_mantra);
            // unnecessary, use android:textSize="24sp"
            // if android sdk api<28 (android 8.0)
            /*
            if (Build.VERSION.SDK_INT<28) {
                tvTitle.setTextSize(24);
                tvMantra.setTextSize(24);
            }
            */
        }
    }
}

com.dedetok.kramaning.FragmentMain.java

package com.dedetok.kramaning;

import android.os.Bundle;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class FragmentMain extends Fragment {

    private String localLang="id";

    private MyAdapter myAdapter;

    public FragmentMain(String localLang) {
        super(R.layout.layout_fragment_main);
        this.localLang = localLang;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        //recyclerview
        RecyclerView myRecyclerView = view.findViewById(R.id.my_recycler_view);
        LinearLayoutManager myLinearLayout = new LinearLayoutManager(getActivity().getApplicationContext());
        myLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
        myRecyclerView.setLayoutManager(myLinearLayout);
        myAdapter = new MyAdapter(localLang);
        myRecyclerView.setAdapter(myAdapter);

    }


    public void updateList(int mFirst, int mLast) {
        myAdapter.updateList(mFirst, mLast);
    }


}

com.dedetok.kramaning.MainActivity.java

package com.dedetok.kramaning;

import androidx.annotation.NonNull;

import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;

import android.os.Bundle;
import android.view.MenuItem;
import com.google.android.material.navigation.NavigationView;

import java.util.Locale;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;


public class MainActivity extends AppCompatActivity {

    private DrawerLayout myDrawerLayout;

    FragmentMain myFragmentMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // get locale language
        String langLocale = Locale.getDefault().getLanguage();
        //Log.e("dedetok", langLocale); //debug

        // TODO change admob id before publish, put back to edit
        // This CODE make slow down startup
        /*
        MobileAds.initialize(this, new OnInitializationCompleteListener() {
            @Override
            public void onInitializationComplete(@NonNull InitializationStatus initializationStatus) {

            }
        });
        */
        // AD Unit ID: ca-app-pub-0220748109202708/9185950681 -> AdView in Layout
        // AD App ID: ca-app-pub-0220748109202708~3978959687 -> AndroidManifest.xml

        AdView mAdView = findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().build();
        mAdView.loadAd(adRequest);

        Toolbar myToolbar = findViewById(R.id.my_toolbar);
        setSupportActionBar(myToolbar);

        myDrawerLayout = findViewById(R.id.my_drawerlayout);
        ActionBarDrawerToggle myActionBarDrawerToggle = new ActionBarDrawerToggle(this, myDrawerLayout, myToolbar, R.string.nav_open, R.string.nav_close);
        myDrawerLayout.addDrawerListener(myActionBarDrawerToggle);
        myActionBarDrawerToggle.syncState();

        NavigationView myNavigationView = findViewById(R.id.my_navigationview);
        myNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                if (myFragmentMain==null) {
                    //Log.e("dedetok", "null create new"); //debug
                    myFragmentMain = new FragmentMain(langLocale);
                }
                //Log.e("dedetok", item.getTitle().toString()); //debug
                int idItem = item.getItemId();
                if (idItem == R.id.preparation) {
                    //Log.e("dedetok", "Preparation"); //debug
                    myFragmentMain.updateList(0,5);
                } else if (idItem == R.id.trisandya) {
                    myFragmentMain.updateList(6,12);
                } else if (idItem == R.id.kramaning) {
                    myFragmentMain.updateList(13,17);
                } else if (idItem == R.id.metirta) {
                    myFragmentMain.updateList(18,21);
                }
                // close drawer if open
                if (myDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                    myDrawerLayout.close();
                }
                return false;
            }
        });

        myFragmentMain = new FragmentMain(langLocale);
        getSupportFragmentManager().beginTransaction().
                add(R.id.my_fragmentcontainerview, myFragmentMain, null).
                commit();
        //Log.e("dedetok", "create fragment end"); // debug will execute after commit
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (myFragmentMain!=null) {
            myFragmentMain.updateList(0,5);
        }
        //Log.e("dedetok", Integer.toString(Build.VERSION.SDK_INT)); // debug version android 7.0 level api 24
    }
}