Friday, December 29, 2023

Privacy Policy for Kidung Bramara (com.dedetok.bramara)

Privacy Statement

Your privacy is important to us. This privacy statement explains what Kidung Bramara (com.dedetok.bramara) application does, regarding your personal data.

Personal data we collect

Kidung Bramara (com.dedetok.bramara) application does not requesting any information about your personal data.

Network & Internet Connection:

Kidung Bramara (com.dedetok.bramara) application does not mandatory to use internet connection. You can use Kidung Bramara (com.dedetok.bramara) application without Internet connection.

If Internet connection exist, Kidung Bramara (com.dedetok.bramara) application will use it to serving for Google AdMob only.

Contact Us:

If You need to contact Us, here is My email address:

dedetoke2021@gmail.com

Wednesday, December 27, 2023

Privacy Policy for Kidung Turun Tirta (com.dedetok.turuntirta)

Privacy Statement

Your privacy is important to us. This privacy statement explains what Kidung Turun Tirta (com.dedetok.turuntirta) application does, regarding your personal data.

Personal data we collect

Kidung Turun Tirta (com.dedetok.turuntirta) application does not requesting any information about your personal data.

Network & Internet Connection:

Kidung Turun Tirta (com.dedetok.turuntirta) application does not mandatory to use internet connection. You can use Kidung Turun Tirta (com.dedetok.turuntirta) application without Internet connection.

If Internet connection exist, Kidung Turun Tirta (com.dedetok.turuntirta) application will use it to serving for Google AdMob only.

Contact Us:

If You need to contact Us, here is My email address:

dedetoke2021@gmail.com

Tuesday, December 26, 2023

Privacy Policy for Purwakaning (com.dedetok.purwakaning)

 

 

Privacy Statement

Your privacy is important to us. This privacy statement explains what Purwakaning (com.dedetok.purwakaning) application does, regarding your personal data.

Personal data we collect

Purwakaning (com.dedetok.purwakaning) application does not requesting any information about your personal data.

Network & Internet Connection:

Purwakaning (com.dedetok.purwakaning) application does not mandatory to use internet connection. You can use Purwakaning (com.dedetok.purwakaning) application without Internet connection.

If Internet connection exist, Purwakaning (com.dedetok.purwakaning) application will use it to serving for Google AdMob only.

Contact Us:

If You need to contact Us, here is My email address:

dedetoke2021@gmail.com

Android java source code com.dedetok.wargasari

This is base project for com.dedetok.wargasari, com.dedetok.purwakaning, and com.dedetok.pitrapuja listed on Google Play. I tested on Android 7, 8, 9, 10, 11 and 13, I believe no bug or no any code that can caused your privacy data leak. No guarantee if you use apk or other package installer other then google play.

For com.dedetok.turuntirta and com.dedetok.bramara, I keep using MediaPlayer legacy. Source code: https://dedetoknotes.blogspot.com/2024/04/android-java-source-code.html

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

build.gradle.kts (Module: app)

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

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

    defaultConfig {
        applicationId = "com.dedetok.wargasari"
        minSdk = 24
        targetSdk = 34
        versionCode = 202404
        versionName = "2024.04"

        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")
    implementation("com.google.android.gms:play-services-ads-lite:23.0.0")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    // For exposing and controlling media sessions
    implementation("androidx.media3:media3-session:1.3.0")
    // For media playback using ExoPlayer
    implementation("androidx.media3:media3-exoplayer:1.3.0")
    // For HLS playback support with ExoPlayer
    implementation("androidx.media3:media3-exoplayer-hls:1.3.0")
    // UI unstable
    //implementation("androidx.media3:media3-ui:1.3.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"/>

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />


    <!-- 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/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Wargasari"
        tools:targetApi="31"
        android:enableOnBackInvokedCallback="true"
        >

        <!-- android:configChanges="orientation|screenSize" keep state UI -->
        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|screenSize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".myservice.DedetokMediaSessionService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="true"
            >
            <intent-filter>
                <action android:name="androidx.media3.session.MediaSessionService"/>
                <action android:name="android.media.browse.MediaBrowserService"/>
            </intent-filter>
        </service>

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

res -> values -> strings.xml

<resources>
    <string name="app_name">Wargasari</string>

    <string-array name="kidung">
        <item>Ida ratu saking luhur |\nKawula nunas lugrane |\nMangda sampun titiang tandruh |\nMengayat bhatara mangkin |\nTitiang ngaturang pejati |\nCanang suci lan daksina |\nSami sampun puput |\nPretingkahing saji ||</item>
        <item>Asep menyan majegau |\nCendana nuhur dewane |\nMangda ida gelis rawuh |\nMijil saking luhuring langit |\nSampun medabdaban sami |\nMaring giri meru reko |\nAncangan sadulur |\nSami pada ngiring ||</item>
        <item>Bhetarane saking luhur |\nNgegenah ring ambarane |\nPenganggene sarwa murub |\nParekan sami mengiring |\nWidyadara widyadari |\nPada medudon dudonan |\nPrabhawa kumetug |\nAngliwer ring langit ||</item>
        <item>Di bale manike luwung |\nMapanyengker ring telagane |\nKedangingin tunjung putih |\nTunjung abang tunjung putih |\nRing madyaning bale alit |\nIda bhatara mebawos |\nNganggit sekar jepun |\nSekar sane wangi ||</item>
        <item>Ring bale emase parum |\nLinggih ida bhetarane |\nBale emas ngranyab murub |\nUpacara sarwa luwih |\nLeluhure sutra putih |\nIda bhatara mabawos |\nBawose di luhur |\nPacang turun gelis ||</item>
        <item>Asep pejati wus katur |\nMendak ida bhetarane |\nPeneteg lan canang harum |\nCanang gantal canang sari |\nParekan pada menangkil |\nPedak sami nunas ica |\nNyadpada menyungsung |\nNgaturang pelinggih ||</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"
        android:padding="10sp"
        />

    <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/play_button"
            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/5649213488 : layout -->
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:adSize="BANNER"
        app:adUnitId="ca-app-pub-3940256099942544/6300978111"
        android:layout_gravity="end|center"
        />
</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 -> com.dedetok.wargasari.myservice -> DedetokMediaSessionService.java

package com.dedetok.wargasari.myservice;

import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.session.MediaSession;
import androidx.media3.session.MediaSessionService;

/*
 * MediaSessionService part - status ok
 * ExoPlayer inside MediaSession
 */

public class DedetokMediaSessionService extends MediaSessionService {

    /*
     * MediaSession creates a default implementation of MediaSession.Callback that
     * automatically handles all commands a MediaController sends to your player
     */
    MediaSession myMediaSession = null;

    /*
     * Create your Player and MediaSession in the onCreate lifecycle event
     *
     */
    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        // Let system handle focus audio for incoming or outgoing call
        player.setAudioAttributes(AudioAttributes.DEFAULT, true);
        myMediaSession = new MediaSession.Builder(this, player).build();
        // Log.e("dedetok","DedetokMediaSessionService onCreate"); // debug

    }

    /*
     * The user dismissed the app from the recent tasks
     */
    @Override
    public void onTaskRemoved(@Nullable Intent rootIntent) {
        Player player = myMediaSession.getPlayer();
        if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 ) {
            /*
             * stop player if not ready || no media playing
             * else play on background
             */
            player.stop();
            stopSelf();
            // Log.e("dedetok", "onTaskRemoved stop and release resources"); // debug
        }
    }

    /*
     * Remember to release the player and media session in onDestroy
     */
    @Override
    public void onDestroy() {
        // https://developer.android.com/media/media3/session/background-playback
        myMediaSession.getPlayer().release();
        myMediaSession.release();
        myMediaSession = null;
        super.onDestroy();
    }

    /*
     * extends MediaSessionService
     */
    @Nullable
    @Override
    public MediaSession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {
        return myMediaSession;
    }

}

java -> com.dedetok.wargasari.myservice -> MyMediaController.java

package com.dedetok.wargasari.myservice;

import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import com.dedetok.wargasari.R;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;

/*
 * MediaController client part
 */
public class MyMediaController {
    String kidungName = "Wargasari";
    Context appContext;
    MediaController myMediaController = null;
    // myMediaController.getApplicationLooper() Returns the Looper associated
    // with the application thread that's used to access the player and on which
    // player events are received.
    MediaItem myMediaItem;
    MyMediaControllerCallback myMediaControllerCallback;

    public MyMediaController(Context appContext, MyMediaControllerCallback myMediaControllerCallback) {
        this.appContext = appContext;
        this.myMediaControllerCallback = myMediaControllerCallback;
        createMediaController(appContext);
    }

    // user send play media
    public void play() {
        if (myMediaController!=null) {
            // pause does not state player i.e. Player.STATE_READY
            if (myMediaController.getPlaybackState()!=Player.STATE_READY) {
                MediaMetadata myMediaMetadata = new MediaMetadata.Builder().
                        setTitle(kidungName).
                        setStation(kidungName).
                        setDisplayTitle(kidungName).
                        //setArtworkUri(myUri). // to set background icon may any size
                                build();

                Uri myUri = Uri.parse("android.resource://com.my.package/" + R.raw.wargasari);
                myMediaItem = new MediaItem.Builder().
                        setMediaMetadata(myMediaMetadata).
                        setUri(myUri).
                        build();
                myMediaController.setMediaItem(myMediaItem); // addMediaItem(MediaItem myMediaItem)
                myMediaController.prepare();
                myMediaController.setPlayWhenReady(true);
            } else {
                //Log.e("dedetok", "MyMediaController start() direct play"); // debug
                myMediaController.play();
            }
        }
    }

    // user call pause
    // pause does not change onPlaybackStateChanged!
    // Player.STATE_READY 3
    public void pause() {
        if (myMediaController!=null) {
            myMediaController.pause();
        }
    }

    /*
     * user exit application, must stop player
     * after stop() Player.STATE_IDLE / 1
     * Player.STATE_IDLE but not trigger onIsPlayingChanged(boolean isPlaying) to false, BUG?
     */
    public void stopPlayer() {
        myMediaController.stop();
    }

    // 20240422
    public void setRepeat(boolean booleanRepeat) {
        if (myMediaController!=null) {
            if (booleanRepeat) {
                myMediaController.setRepeatMode(Player.REPEAT_MODE_ONE);
            } else {
                myMediaController.setRepeatMode(Player.REPEAT_MODE_OFF);
            }
        }
    }

    // call when exit application
    public void releaseMediaSession() {
        myMediaController.release();
    }

    /*
     * ContextCompat.getMainExecutor() instead of MoreExecutors.directExecutor()
     * in case you run your service in another process than the UI.
     */
    private void createMediaController(Context appContext) {
        //Log.e("dedetok","createMediaController step 1"); // debug
        // https://developer.android.com/media/media3/session/connect-to-media-app#create-controller
        // step 1 create SessionToken
        SessionToken mySessionToken =
                new SessionToken(appContext,
                        new ComponentName(appContext, DedetokMediaSessionService.class));
        //Log.e("dedetok","createMediaController step 2"); // debug
        // step 2 build MediaController using SessionToken
        ListenableFuture<MediaController> myListenableFutureMediaController =
                new MediaController.Builder(appContext, mySessionToken).buildAsync();
        myListenableFutureMediaController.addListener(() -> {
            // MediaController is available here with myListenableFutureMediaController.get()
            try {
                //Log.e("dedetok","try to get Media Controller"); // debug
                myMediaController = myListenableFutureMediaController.get();

                myMediaController.addListener(myPlayerListener);
            } catch(ExecutionException | InterruptedException e) {
                //Log.e("dedetok", e.getMessage()); // debug

            }
        }, MoreExecutors.directExecutor());
    }

    /*
     * Playback listener 20240229
     */
    Player.Listener myPlayerListener = new Player.Listener() {
        @Override
        public void onPlaybackStateChanged(int playbackState) {
            Player.Listener.super.onPlaybackStateChanged(playbackState);
            if (playbackState == Player.STATE_IDLE)
                Log.e("dedetok", "Player.STATE_IDLE 1"); // debug
            else if (playbackState == Player.STATE_BUFFERING)
                Log.e("dedetok", "Player.STATE_BUFFERING 2"); // debug
            else if (playbackState == Player.STATE_READY)
                Log.e("dedetok", "Player.STATE_READY 3"); // debug
            else if (playbackState == Player.STATE_ENDED)
                Log.e("dedetok", "Player.STATE_ENDED 4"); // debug
        }

        @Override
        public void onIsPlayingChanged(boolean isPlaying) {
            Log.e("dedetok", "onIsPlayingChanged change to "+isPlaying); // debug
            myMediaControllerCallback.isPlaying(isPlaying);

        }
    };

    public interface MyMediaControllerCallback {
        // from MyMediaController change player status
        void isPlaying(boolean isPlaying);
    }
}

java -> MyDataProvider.java

package com.dedetok.wargasari;

import static android.util.TypedValue.COMPLEX_UNIT_SP;
import android.content.Context;
import android.content.res.TypedArray;
import com.dedetok.wargasari.myservice.MyMediaController;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyDataProvider implements MyMediaController.MyMediaControllerCallback {
    MyMediaController myMediaController;
    Context appContext;
    MyDataProviderCallback myDataProviderCallback;
    ExecutorService executor;
    MyAdapter myAdapter;
    boolean isPlaying;

    public MyDataProvider(Context appContext) {
        this.appContext = appContext;
        this.myDataProviderCallback = (MyDataProvider.MyDataProviderCallback) appContext;
        myMediaController = new MyMediaController(appContext, this);
    }

    /*
     * async task to create recyclerview.adapter
     * load array text into adapter
     */
    public void loadKidungArrayAdapter(TypedArray availFontSize, int cTextSizeState, float defaultTextSize) {
        if (executor == null) {
            executor = Executors.newSingleThreadExecutor();
        }
        Runnable myRun = new Runnable() {
            @Override
            public void run() {
                myAdapter = new MyAdapter(getTexts(), COMPLEX_UNIT_SP, availFontSize.getFloat(cTextSizeState, defaultTextSize));
                myDataProviderCallback.setKidungArrayAdapter(myAdapter);
                executor.shutdown();
                executor = null;
            }
        };
        executor.execute(myRun);
    }

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

    /*
     * user play media
     * send command play to media controller
     */
    public void play() {
        if (myMediaController!=null) {
            //Log.e("dedetok", "MyDataProvider Playing"); // debug
            // player does not instanly ready
            myMediaController.play();
        }
    }

    // user call pause
    public void pause() {
        if (myMediaController!=null) {
            myMediaController.pause();
        }
    }

    // player exit application must stop player
    public void stopPlayer() {
        myMediaController.stopPlayer();
    }

    // 20240422
    public void setRepeat(boolean booleanRepeat) {
        myMediaController.setRepeat(booleanRepeat);
    }

    // call when application destroy
    public void releaseMediaSession() {
        myMediaController.releaseMediaSession();
    }

    // user change font size
    public void changeFontSize(float fSize) {
        myAdapter.changeFontSize(COMPLEX_UNIT_SP,fSize);
    }

    /*
     * implementation MyMediaControllerCallback START
     */
    // from MyMediaController change player status
    @Override
    public void isPlaying(boolean isPlaying) {
        this.isPlaying=isPlaying;
        myDataProviderCallback.isPlaying(isPlaying);
    }

    /*
     * implementation MyMediaControllerCallback START
     */

    public interface MyDataProviderCallback {
        // from MyDataProvider to update recyclerview
        void setKidungArrayAdapter(MyAdapter myAdapter);
        void isPlaying(boolean isPlaying);
    }
}

java -> MainActivity.java

package com.dedetok.wargasari;

import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageButton;
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.TypedArray;
import android.os.Bundle;
import android.view.View;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;

public class MainActivity extends AppCompatActivity implements
        MyDataProvider.MyDataProviderCallback {

    int cTextSizeState = 0;
    TypedArray availFontSize;
    float defaultTextSize = 18;
    RecyclerView myRecyclerView;
    MyDataProvider myDataProvider;
    AppCompatImageButton bPlayPause;

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

        myDataProvider = new MyDataProvider(this);

        // 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/5649213488 : AdView in Layout
        // app id ca-app-pub-0220748109202708~2695747083 : AndroidManifest.xml
        AdView mAdView = findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().build();
        mAdView.loadAd(adRequest);

        // recyclerview
        availFontSize = getResources().obtainTypedArray(R.array.my_font_size);
        myRecyclerView = findViewById(R.id.my_recycler_view);
        LinearLayoutManager myLinearLayout = new LinearLayoutManager(getApplicationContext());
        myLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
        myRecyclerView.setLayoutManager(myLinearLayout);
        // adapter will created on async task

        // 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) {
                    cTextSizeState--;
                    if (myDataProvider!=null) {
                        myDataProvider.changeFontSize(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()) {
                    cTextSizeState++;
                    if (myDataProvider!=null) {
                        myDataProvider.changeFontSize(availFontSize.getFloat(cTextSizeState, defaultTextSize));
                    }
                }
            }
        });

        // 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 myAliertDialog = new AlertDialog.Builder(
                        MainActivity.this);
                myAliertDialog.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);

        // 20240328
        bPlayPause = findViewById(R.id.play_button);
        bPlayPause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (myDataProvider!=null) {
                    if (myDataProvider.isPlaying) {
                        myDataProvider.pause();
                    } else {
                        myDataProvider.play();
                    }
                }
            }
        });

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

    }

    // ## activity life cycle 2 ##
    // onStart()
    @Override
    protected void onStart() {
        super.onStart();
        // 20240329
        // Android N (7.0) or latter MediaPlayer should create in OnStart
        // TypedArray availFontSize, int cTextSizeState, float defaultTextSize)
        // create Adapter in async task
        myDataProvider.loadKidungArrayAdapter(availFontSize, cTextSizeState, defaultTextSize);
    }

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

    /*
     * ## activity life cycle 4 ##
     * onPause()
     * @Override onSaveInstanceState(Bundle outState) ?
     */
    @Override
    public void onPause() {
        // do something before pause
        //Log.e("dedetok","onPause"); // debug
        super.onPause();
    }

    // ## activity life cycle 5 ##
    @Override
    protected void onStop() {
        // 20240329
        // Android N (7.0) or latter MediaPlayer should stop and release in OnStop
        // do something before stop
        //Log.e("dedetok","onStop"); // debug
        super.onStop();
    }

    /*
     * ## activity life cycle 6 ##
     * clean up onDestroy()
     */
    @Override
    public void onDestroy() {
        // Log.e("dedetok","onDestroy Activity"); // debug
        // do something before destroy
        //Log.e("dedetok","onDestroy"); // debug
        // fix android 8, 9, 10, 11 to stop media and release resource
        myDataProvider.stopPlayer();
        myDataProvider.releaseMediaSession();
        super.onDestroy();
    }

    /*
     * impelementation MyDataProvider START
     */
    // from MyDataProvider to update recyclerview
    @Override
    public void setKidungArrayAdapter(MyAdapter myAdapter) {
        // can not direct update
        myRecyclerView.post(new Runnable() {
            @Override
            public void run() {
                myRecyclerView.setAdapter(myAdapter);
            }
        });
    }

    public void isPlaying(boolean isPlaying) {
        bPlayPause.post(new Runnable() {
            @Override
            public void run() {
                if (isPlaying) {
                    bPlayPause.setImageDrawable(getDrawable(android.R.drawable.ic_media_pause));
                } else {
                    bPlayPause.setImageDrawable(getDrawable(android.R.drawable.ic_media_play));
                }
            }
        });
    }

    /*
     * impelementation MyDataProvider END
     */

}


Wednesday, December 20, 2023

Android Studio Hedgehog | 2023.1.1: import java library problem

Causing: Unknown

Symptoms: Can not import any java library

Option:

  • File > Invalidate Caches... -> clear all (Working on my project)
  • File > Sync Project with Gradle Files (not tested)

https://stackoverflow.com/questions/45982092/android-studio-not-recognizing-import-statements

Android Studio Hedgehog | 2023.1.1: manual backup android project

Manual backup android studio 2023 project 

Root project files:

  • build.gradle.kts
  • gradle.properties
  • gradlew
  • gradle.bat
  • local.properties
  • settings.gradle.kts
  • *.jks (java keystore files if you publish your application with signed)

folders root project -> app:

  • build.gradle.kts
  • proguard-rules.pro
  • src (all)

Tuesday, December 19, 2023

Privacy Policy for Kramaning (com.dedetok.kramaning)

Privacy Statement

Your privacy is important to us. This privacy statement explains what Kramaning (com.dedetok.kramaning) application does, regarding your personal data.

Personal data we collect

Kramaning (com.dedetok.kramaning) application does not requesting any information about your personal data.

Network & Internet Connection:

Kramaning (com.dedetok.kramaning) application does not mandatory to use internet connection. You can use Kramaning (com.dedetok.kramaning) application without Internet connection.

If Internet connection exist, Kramaning (com.dedetok.kramaning) application will use it to serving for Google AdMob only.

Contact Us:

If You need to contact Us, here is My email address:

dedetoke2021@gmail.com

Monday, December 18, 2023

Android java: DrawerLayout, Toolbar & NavigationView in java

 

Android Studio Hedgehog | 2023.1.1

Note: For personal reference using DrawerLayout, Toolbar & NavigationView in java

Create new Project (No Activity)

Name TutorDrawerMenu
Package Name com.dedetok.tutordrawermenu

Create Activity -> Empty View Activity

Activity Name: Main Activity
Layout Name activity_main
Laucher Activity checked

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"/>

    <!-- apply android:theme="@style/Theme.AppCompat.Light.NoActionBar" at AndroidManifest.xml -->
    <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"
        >
        <!--  android:theme="@style/Theme.AppCompat.Light.NoActionBar" setSupportActionBar(myToolbar); -->

        <activity
...
        </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>

build.gradle.kts (Module: app)

dependencies {
...
    implementation("com.google.android.gms:play-services-ads-lite:22.6.0")
...

Resource -> Values -> strings.xml

<resources>
    <string name="app_name">Tutorial Drawer Menu</string>
    <string name="nav_open">Open</string>
    <string name="nav_close">Close</string>
</resources>

 Resource -> Menu -> my_menu.xml

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

    <item android:id="@+id/item_1" android:title="Item 1" />
    <item android:id="@+id/item_2" android:title="Item 2" />
</menu>

 Layout

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- androidx.drawerlayout.widget.DrawerLayout must be put on top of main layout -->
<androidx.drawerlayout.widget.DrawerLayout
    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"
    android:id="@+id/my_drawerlayout"
    tools:openDrawer="start"
    android:fitsSystemWindows="true"
    >

    <!-- must have layout -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >
        <!-- put androidx.appcompat.widget.Toolbar at the first of layout -->
        <androidx.appcompat.widget.Toolbar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/my_toolbar"
            />

        <!-- PUT CONTENT HERE -->

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

        <!-- EXAMPLE FOR androidx.fragment.app.FragmentContainerView  add android:layout_weight="1" and android:gravity="top|left" -->

        <!-- lock adview position at bottom of layout -->
        <!-- Sample AdMob unit ID: ca-app-pub-3940256099942544/6300978111 -->
        <!-- AD Unit ID: ca-app-pub-0220748109202708/9185950681 -->
        <com.google.android.gms.ads.AdView
            android:id="@+id/adView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:adSize="BANNER"
            app:adUnitId="ca-app-pub-0220748109202708/9185950681"
            android:layout_gravity="end|center"
            />

    </LinearLayout>

    <!-- com.google.android.material.navigation.NavigationView must be put at the end of main layout -->
    <com.google.android.material.navigation.NavigationView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/my_navigationview"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:menu="@menu/my_menu"
        />

</androidx.drawerlayout.widget.DrawerLayout>

MainActivity.java

package com.dedetok.tutordrawermenu;

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.util.Log;
import android.view.MenuItem;

import com.google.android.material.navigation.NavigationView;

public class MainActivity extends AppCompatActivity {

    DrawerLayout myDrawerLayout;

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

        Toolbar myToolbar = findViewById(R.id.my_toolbar);
        setSupportActionBar(myToolbar); // android:theme="@style/Theme.AppCompat.Light.NoActionBar"

        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) {
                Log.e("dedetok", item.getTitle().toString());
                int idItem = item.getItemId();
                if (idItem==R.id.item_1) {
                    // Do something
                } else if (idItem==R.id.item_2) {
                    // Do something
                }
                if (myDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                    myDrawerLayout.close();
                }
                return false;
            }
        });
    }

}

NOTE: Some code may reference to other source code in comment (some has modified to make it run), do not remove urls if you wish to copy paste code and use the code.

 

Sablon manual screen

Screen

Ukuran screen yang siap pakai

  1. 20x30
  2. 40x60
  3. lebih besar

Kerapatan screen

kerapatanmedia yang disablon
T35, T30, T36, T40
handuk, karung
T48, T54, T61, T62, T65, T77
kain, kaos
T90, T120
kayu, gelas
T150, T165, T180, T200 (200s)
kertas, plastik, stiker

Bahan afdruk screen

Obat afdruk 

  1. Diazol
  2. Rainbow
  3. Ulano
  4. Photazol
  5. TX-super
  6. Meta-X
  7. Bremol tex (cat water) / Bremol RN (cat minyak/plastisol)

Ulano

Afdruk Ulano

  1. oil resistence (untuk cat berbasis minyak/plastisol): Ulano 133, 569, FX88, QFX, DLX, QX1, QX3
  2. water resistence (untuk cat berbasis air): Ulano TZ, QTZ, QX5
  3. water & oil resistence: Ulano LX 660, LX758, LX 836, LX892

Fabric abrader,& de-greaser Ulano 3, Gel 23

Remover Ulano 4 (liquid) & Ulano 5 (pasta)

Fabric regeneartor Ulano 8

Screen filler Ulano 6

Hardener afdruk Ulano X

Bahan kimia lain

Bahan kimia lain untuk reducer (menghapus) screen

  1. soda api 1:1
  2. pregant paste 1:1
  3. reducer pvc
  4. natrium hipolorit

Tinta

Bedasarkan media sablon:

  1. kain
  2. kertas
  3. kayu, seng, triplex
  4. kulit, mika

Tool

Alat pendukung lainnya

  1. rakel
  2. meja sablon
  3. gelas, mangkuk, botol gelap, alat aduk (sendok plastik)
  4. kaca bening penekan negatif
  5. busa penyangga screen
  6. triplex untuk menekan busa ke screen

Penyablonan

  1. Desain gambar yang akan disablon dikertas tembus cahaya UV, Bila desain diceta di kertas HVS, gunakan minyak goreng bersih untuk mengolesi hasil print di kertas HVS. posisi gambar yang disablon mirror kiri kanan.
  2. Olesi screen dengan bahan afdruk secara merata dan tunggu kering 
  3. Susun screen dengan urutan paling atas
    1. kaca penjepit
    2. desain gambar
    3. screen
    4. busa
    5. triplex
  4. Sinari dibawah terik matahari +- 30 detik (tergantung kecerahan), lampu UV 40 watt selama 3-10 menit
  5. perbaiki negatif bila ada yang rusak
  6. siram dengan air panas dan semprot dengan air bersih


Referensi

  1. website: centralsps.co.id
  2. website: semua25.blogspot.com 
  3. website: oscas.co.id
  4. pdf "Teknik Dasar Cetak Sablon", Noor Fitrihana, ST & Widihastuti, M.Pd
  5. pdf "Teknik Pencelupan dan Pencapan untuk Sekolah Menengah Kejuruan Jilid 3", Sunarto, Direktorat Pembinaan Sekolah Menengah Kejuruan


Wednesday, December 13, 2023

Android java: SQLiteOpenHelper, Fragment & RecyclerView in java

 

Android Studio Hedgehog | 2023.1.1

Note: For personal reference using SearchView, Filter (text), SQLiteOpenHelper, RecyclerView and Fragment

Create new Project (No Activity)

Name CA Price List
Package Name com.dedetok.capricelist

Create Activity -> Empty View Activity

Activity Name: Main Activity
Layout Name activity_main
Laucher Activity checked

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 >= android 6 api level 23 -->
    <uses-permission-sdk-23 android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission-sdk-23 android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <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.CAPriceList"
        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>
    </application>

</manifest>

build.gradle.kts(Module :app)

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

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

    defaultConfig {
        applicationId = "com.dedetok.capricelist"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
,,,

Resource -> Values -> strings.xml

<resources>
    <string name="app_name">CA Price List</string>
    <string name="t_cancel">Cancel</string>
    <string name="t_save">Save</string>
    <string name="t_delete">Delete</string>
    <string name="t_product_name">Product Name</string>
    <string name="t_product_price">Product Price</string>
    <string name="t_product_code">Product Code</string>
    <string name="t_product_id">Product ID</string>
    <string name="t_new_product">New Product</string>
</resources>

Menu

my_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id = "@+id/menu_add_pricelist"
        android:title="@string/t_new_product"
        />
</menu>

Layout

layout_price_list.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:id="@+id/pricelist_item"
    android:minHeight="48dp"
    android:layout_margin="5dp"
    >

</TextView>

layout_fragment_list.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"
    android:orientation="vertical"
    >

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

layout_fragment_detail.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"
    android:orientation="vertical">

    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:id="@+id/product_id"
        android:text="@string/t_product_id"
        android:layout_margin="15dp"
        />
    <EditText
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:id="@+id/product_name"
        android:minHeight="48dp"
        android:hint="@string/t_product_name"
        android:autofillHints="@string/t_product_name"
        android:inputType="text"
        android:layout_margin="15dp"
        />

    <EditText
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:id="@+id/product_price"
        android:minHeight="48dp"
        android:autofillHints="@string/t_product_price"
        android:hint="@string/t_product_price"
        android:inputType="text"
        android:layout_margin="15dp"
        />
    <EditText
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:id="@+id/product_code"
        android:minHeight="48dp"
        android:autofillHints="@string/t_product_code"
        android:hint="@string/t_product_code"
        android:inputType="text"
        android:layout_margin="15dp"
        />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_margin="15dp"
        android:gravity="center_horizontal"
        >
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/t_cancel"
            android:id="@+id/b_cancel_fragment_detail"
            android:contentDescription="@string/t_cancel"
            android:layout_margin="5dp"
            />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/t_save"
            android:id="@+id/b_save_fragment_detail"
            android:contentDescription="@string/t_save"
            android:layout_margin="5dp"
            />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/t_delete"
            android:id="@+id/b_delete_fragment_detail"
            android:contentDescription="@string/t_delete"
            android:layout_margin="5dp"
            />
    </LinearLayout>
</LinearLayout>

activity_main.xml

<?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">

    <androidx.fragment.app.FragmentContainerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/my_fragmentcontainerview"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

Create java package: New -> Packages : com.dedetok.capricelist.DBHelper

Java in com.dedetok.capricelist.DBHelper packages

PriceList.java

package com.dedetok.capricelist.DBHelper;


/*
 * Table for price_list
 */
public class PriceList {
    public static final String TABLENAME = "price_list";

    /*
     * column name and mapping
     */
    // Primary Key
    public static final String IDPRODUCT = "id_product";
    public long idProduct;

    public static final String NAMEPRODUCT = "name_product";
    public String nameProduct;

    public static final String PRICEPRODUCT = "price_product";
    public String priceProduct;

    public static final String CODEPRODUCT = "code_product";
    public String codeProduct;

    //debug
    public String getText() {
        return idProduct+" "+nameProduct+" "+priceProduct+" "+codeProduct;
    }
}

MySQLiteHelper.java

package com.dedetok.capricelist.DBHelper;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.annotation.Nullable;

public class MySQLiteHelper extends SQLiteOpenHelper {

    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "CAPriceList.db";
    private static final String SQLCREATEDB = "create TABLE "+PriceList.TABLENAME +"(" +
            PriceList.IDPRODUCT+ " INTEGER PRIMARY KEY AUTOINCREMENT," +
            PriceList.NAMEPRODUCT+ " TEXT NOT NULL," +
            PriceList.PRICEPRODUCT+ " TEXT NOT NULL," +
            PriceList.CODEPRODUCT+ " Text);";

    public MySQLiteHelper(@Nullable Context myAppContext, @Nullable String myDBName, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(myAppContext, myDBName, factory, version);
    }

    /*
     * Mandatory to create   table
     */
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(SQLCREATEDB);

    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        // TODO

    }
}

BoyerMooreTextSearch.java -> fast match string better then indexOf() or contain()

package com.dedetok.capricelist.DBHelper;

public class BoyerMooreTextSearch {
    /**
     * https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm#Java_implementation
     *
     * Returns the index within this string of the first occurrence of the
     * specified substring. If it is not a substring, return -1.
     *
     * There is no Galil because it only generates one match.
     *
     * @param haystack The string to be scanned
     * @param needle The target string to search
     * @return The start index of the substring
     */
    public static int indexOf(char[] haystack, char[] needle) {
        if (needle.length == 0) {
            return 0;
        }
        int charTable[] = makeCharTable(needle);
        int offsetTable[] = makeOffsetTable(needle);
        for (int i = needle.length - 1, j; i < haystack.length;) {
            for (j = needle.length - 1; needle[j] == haystack[i]; --i, --j) {
                if (j == 0) {
                    return i;
                }
            }
            // i += needle.length - j; // For naive method
            i += Math.max(offsetTable[needle.length - 1 - j], charTable[haystack[i]]);
        }
        return -1;
    }

    /**
     * Makes the jump table based on the mismatched character information.
     */
    private static int[] makeCharTable(char[] needle) {
        final int ALPHABET_SIZE = Character.MAX_VALUE + 1; // 65536
        int[] table = new int[ALPHABET_SIZE];
        for (int i = 0; i < table.length; ++i) {
            table[i] = needle.length;
        }
        for (int i = 0; i < needle.length; ++i) {
            table[needle[i]] = needle.length - 1 - i;
        }
        return table;
    }

    /**
     * Makes the jump table based on the scan offset which mismatch occurs.
     * (bad-character rule).
     */
    private static int[] makeOffsetTable(char[] needle) {
        int[] table = new int[needle.length];
        int lastPrefixPosition = needle.length;
        for (int i = needle.length; i > 0; --i) {
            if (isPrefix(needle, i)) {
                lastPrefixPosition = i;
            }
            table[needle.length - i] = lastPrefixPosition - i + needle.length;
        }
        for (int i = 0; i < needle.length - 1; ++i) {
            int slen = suffixLength(needle, i);
            table[slen] = needle.length - 1 - i + slen;
        }
        return table;
    }

    /**
     * Is needle[p:end] a prefix of needle?
     */
    private static boolean isPrefix(char[] needle, int p) {
        for (int i = p, j = 0; i < needle.length; ++i, ++j) {
            if (needle[i] != needle[j]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the maximum length of the substring ends at p and is a suffix.
     * (good-suffix rule)
     */
    private static int suffixLength(char[] needle, int p) {
        int len = 0;
        for (int i = p, j = needle.length - 1;
             i >= 0 && needle[i] == needle[j]; --i, --j) {
            len += 1;
        }
        return len;
    }

}

Java in com.dedetok.capricelist packages

DBCARepository.java

package com.dedetok.capricelist;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.dedetok.capricelist.DBHelper.MySQLiteHelper;
import com.dedetok.capricelist.DBHelper.PriceList;
import java.util.ArrayList;

// https://www.geeksforgeeks.org/singleton-class-in-android/
// https://www.androidcode.ninja/android-sqlite-transaction-tutorial/


public class DBCARepository {

    private static MySQLiteHelper mySQLiteHelper = null;

    // private static instance variable to hold the singleton instance
    private static volatile DBCARepository myDBCARepository = null;

    // private constructor to prevent instantiation of the class
    private DBCARepository(Context appContext) {
        if (mySQLiteHelper == null) {
            mySQLiteHelper = new MySQLiteHelper(appContext,
                MySQLiteHelper.DATABASE_NAME, null,
                MySQLiteHelper.DATABASE_VERSION);
        }
    }

    // public static method to retrieve the singleton instance
    public static DBCARepository getInstance() {
        return myDBCARepository;
    }

    public static void prepareDB(Context myAppContext) {
        // Check if the instance is already created
        if(myDBCARepository == null) {
            // synchronize the block to ensure only one thread can execute at a time
            synchronized (DBCARepository.class) {
                // check again if the instance is already created
                if (myDBCARepository == null) {
                    // create the singleton instance
                    myDBCARepository = new DBCARepository(myAppContext);
                }
            }
        }
    }

    public ArrayList<PriceList> getPriceLists() {
        // https://developer.android.com/training/data-storage/sqlite
        SQLiteDatabase myDB = mySQLiteHelper.getReadableDatabase();

        String[] projection =   {
                PriceList.IDPRODUCT, //0
                PriceList.NAMEPRODUCT, //1
                PriceList.PRICEPRODUCT, //2
                PriceList.CODEPRODUCT //3
        };

        // Filter results WHERE "title" = 'My Title'
        // String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
        String selection = null; // Not Used set null
        // String[] selectionArgs = { "My Title" };
        String[] selectionArgs = null; // Not used set null

        // How you want the results sorted in the resulting Cursor
        //String sortOrder =
        //        FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
        String sortOrder = null; // Not used set null

        Cursor cursor =  myDB.query(
                PriceList.TABLENAME,
                projection,             // The array of columns to return (pass null to get all)
                selection,              // The columns for the WHERE clause
                selectionArgs,          // The values for the WHERE clause
                null,                   // don't group the rows
                null,                   // don't filter by row groups
                sortOrder               // The sort order
        );

        ArrayList itemList = new ArrayList<>();
        while(cursor.moveToNext()) {
            PriceList mPriceList = new PriceList();
            mPriceList.idProduct = cursor.getLong(0);
            //Log.e("dedetok", Long.toString(cursor.getLong(0))); // debug
            mPriceList.nameProduct = cursor.getString(1);
            //Log.e("dedetok", cursor.getString(1)); // debug
            mPriceList.priceProduct = cursor.getString(2);
            mPriceList.codeProduct = cursor.getString(3);
            itemList.add(mPriceList);
            //Log.e("dedetok", "getPriceLists()"+ mPriceList.getText()); //debug
        }
        cursor.close();
        myDB.close();

        return itemList;
    }

    /*
     * insert a new Price List, ID is auto number
     */
    public long insertPriceList(PriceList newPriceList) {
        //Log.e("dedetok", "DBACARepository insertPriceList get DB"); // debug
        SQLiteDatabase myDB = mySQLiteHelper.getWritableDatabase();
        ContentValues myContent = new ContentValues();
        myContent.put(PriceList.NAMEPRODUCT, newPriceList.nameProduct);
        myContent.put(PriceList.PRICEPRODUCT, newPriceList.priceProduct);
        myContent.put(PriceList.CODEPRODUCT, newPriceList.codeProduct);
        //Log.e("dedetok", "DBACARepository insertPriceList insert data"); // debug
        long retValue = myDB.insert(PriceList.TABLENAME, null, myContent);
        //Log.e("dedetok", "DBACARepository insertPriceList insert data result "+retValue); // debug
        //Log.e("dedetok", "DBACARepository insertPriceList close db"); // debug
        myDB.close();
        return retValue;
    }

    // Save / update
    /*
     * To update price list after editing
     */
    public int updatePriceList(PriceList myPriceList) {
        SQLiteDatabase myDB = mySQLiteHelper.getWritableDatabase();
        ContentValues myContent = new ContentValues();
        myContent.put(PriceList.IDPRODUCT, myPriceList.idProduct);
        myContent.put(PriceList.NAMEPRODUCT, myPriceList.nameProduct);
        myContent.put(PriceList.PRICEPRODUCT, myPriceList.priceProduct);
        myContent.put(PriceList.CODEPRODUCT, myPriceList.codeProduct);
        // Filter results WHERE "title" = 'My Title'
        // String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
        String selection = PriceList.IDPRODUCT + " = ?";
        // String[] selectionArgs = { "My Title" };
        String[] selectionArgs = {Long.toString(myPriceList.idProduct)}; // Not used set null
        int retVal = myDB.update(PriceList.TABLENAME, myContent, selection, selectionArgs);
        return retVal;
    }

    /*
     * delete
     */
    public int deletePriceList(PriceList myPriceList) {
        SQLiteDatabase myDB = mySQLiteHelper.getWritableDatabase();
        // Filter results WHERE "title" = 'My Title'
        // String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
        String selection = PriceList.IDPRODUCT + " = ?";
        // String[] selectionArgs = { "My Title" };
        String[] selectionArgs = {Long.toString(myPriceList.idProduct)};
        int retVal = myDB.delete(PriceList.TABLENAME, selection, selectionArgs);
        return retVal;
    }
}

PriceListsAdapter.java

package com.dedetok.capricelist;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;

import com.dedetok.capricelist.DBHelper.BoyerMooreTextSearch;
import com.dedetok.capricelist.DBHelper.PriceList;

import java.util.ArrayList;

// https://developer.android.com/develop/ui/views/layout/recyclerview

public class PriceListsAdapter extends RecyclerView.Adapter implements Filterable {

    // variable
    private ArrayList<PriceList> myPriceListsFiltered;
    private ArrayList<PriceList> myPriceListsOriginal;

    /*
     * construtor to initilize array list
     */
    public PriceListsAdapter() {
        myPriceListsOriginal = DBCARepository.getInstance().getPriceLists();
        myPriceListsFiltered = myPriceListsOriginal;
    }

    /*
     * mandatory extending RecyclerView.Adapter
     * constructor
     */
    @NonNull
    @Override
    public PriceListsAdapter.PriceListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // Create a new view, which defines the UI of the list item
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.layout_price_list, parent, false);
        return new PriceListsAdapter.PriceListHolder (view);
    }

    /*
     * mandatory extending RecyclerView.Adapter
     */
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        PriceList myPriceList = myPriceListsFiltered.get(position);
        String tmpString = Long.toString(myPriceList.idProduct)+" "+
                myPriceList.nameProduct+" "+
                myPriceList.priceProduct+" "+
                myPriceList.codeProduct;
        ((PriceListHolder) holder).textView.setText(tmpString);
        // https://www.geeksforgeeks.org/how-to-apply-onclicklistener-to-recyclerview-items-in-android/
        holder.itemView.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        PriceList mPriceList = myPriceListsFiltered.get(holder.getAdapterPosition());
                        FragmentDetail myFragmentDetail = new FragmentDetail(mPriceList);
                        ((FragmentActivity) view.getContext()).getSupportFragmentManager().
                                beginTransaction().
                                replace(R.id.my_fragmentcontainerview, myFragmentDetail).
                                addToBackStack("List").
                                commit();


                        //Log.e("dedetok", "position "+ Integer.toString(holder.getAdapterPosition())); // debug
                    }
                }
        );

    }

    /*
     * mandatory extending RecyclerView.Adapter
     * return number of item on ArrayList<PriceList> myPriceLists
     */
    @Override
    public int getItemCount() {
        return myPriceListsFiltered.size();
    }

    /*
     * implements android.widget.Filterable;
     * https://codingwithmitch.com/blog/filtering-recyclerview-searchview/
     */
    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                String tmpPattern = charSequence.toString().toUpperCase();

                if (tmpPattern.isEmpty()) {
                    myPriceListsFiltered = myPriceListsOriginal;
                } else {
                    myPriceListsFiltered = new ArrayList<PriceList>();
                    for (PriceList myPriceList : myPriceListsOriginal) {
                        String tmpText = myPriceList.nameProduct.toUpperCase();
                        if (BoyerMooreTextSearch.indexOf(tmpText.toCharArray(), tmpPattern.toCharArray())> -1) {
                            myPriceListsFiltered.add(myPriceList);
                        }
                    }
                }
                FilterResults results = new FilterResults();
                results.values = myPriceListsFiltered;
                return results;
            }

            @Override
            protected void publishResults(CharSequence charSequence, FilterResults results) {
                myPriceListsFiltered = (ArrayList<PriceList>) results.values;
                notifyDataSetChanged();
            }
        };
    }

    private class PriceListHolder extends RecyclerView.ViewHolder {

        public TextView textView;

        /*
         * mandatory extends RecyclerView.ViewHolder
         */
        public PriceListHolder(@NonNull View view) {
            super(view);
            textView = view.findViewById(R.id.pricelist_item);
        }


    }
}

FragmentDetail.java

package com.dedetok.capricelist;

import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;

import com.dedetok.capricelist.DBHelper.PriceList;

public class FragmentDetail extends Fragment {

    PriceList myPriceList=null;

    /*
     * constructor to inflate fragment
     */
    public FragmentDetail() {
        super(R.layout.layout_fragment_detail);
    }

    public FragmentDetail(PriceList myPriceList) {
        super(R.layout.layout_fragment_detail);
        this.myPriceList = myPriceList;
    }

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

        // EditText
        EditText etProductName = view.findViewById(R.id.product_name);
        EditText etProductPrice = view.findViewById(R.id.product_price);
        EditText etProductCode = view.findViewById(R.id.product_code);
        if (myPriceList!=null) {
            // if edit or delete data
            TextView tvProductID = view.findViewById(R.id.product_id);
            tvProductID.setText(myPriceList.codeProduct);
            etProductName.setText(myPriceList.nameProduct);
            etProductPrice.setText(myPriceList.priceProduct);
            etProductCode.setText(myPriceList.codeProduct);
        }

        // button cancel
        Button bCancelFragmentDetail = view.findViewById(R.id.b_cancel_fragment_detail);
        bCancelFragmentDetail.setOnClickListener(cancelFragmentDetail -> getParentFragmentManager().popBackStack());

        // button save
        Button bSaveFragmentDetail = view.findViewById(R.id.b_save_fragment_detail);
        bSaveFragmentDetail.setOnClickListener(saveFragmentDetail -> {
            if (myPriceList ==null) {
                // new data
                //Log.e("dedetok", "FragmentDetail insert data"); //debug
                PriceList newPriceList = new PriceList();
                newPriceList.nameProduct = etProductName.getText().toString();
                newPriceList.priceProduct = etProductPrice.getText().toString();
                newPriceList.codeProduct = etProductCode.getText().toString();
                DBCARepository.getInstance().insertPriceList(newPriceList);
                //Log.e("dedetok", "FragmentDetail insert data end"); // debug
                getParentFragmentManager().popBackStack();
            } else {
                // save existing data
                myPriceList.nameProduct = etProductName.getText().toString();
                myPriceList.priceProduct = etProductPrice.getText().toString();
                myPriceList.codeProduct = etProductCode.getText().toString();
                DBCARepository.getInstance().updatePriceList(myPriceList);
                getParentFragmentManager().popBackStack();
            }
        });

        // https://stackoverflow.com/questions/38411879/alert-confirmation-dialog-android
        // https://stackoverflow.com/questions/10207206/how-to-display-alertdialog-in-a-fragment
        Button bDeleteFragmentDetail = view.findViewById(R.id.b_delete_fragment_detail);
        if (myPriceList == null) {
            // new data, we don't have to delete
            bDeleteFragmentDetail.setVisibility(View.INVISIBLE);
        } else {
            bDeleteFragmentDetail.setVisibility(View.VISIBLE);
            bDeleteFragmentDetail.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
                    alert.setTitle("Delete");
                    alert.setMessage("Are you sure you want to delete?");
                    alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            DBCARepository.getInstance().deletePriceList(myPriceList);
                            Log.e("dedetok", "deleted"); // debug
                            dialogInterface.dismiss();
                        }
                    });
                    alert.setNegativeButton("No", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            dialogInterface.dismiss();
                        }
                    });
                    alert.show();
                }
            });
        }
    }
}

FragmentList.java

package com.dedetok.capricelist;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SearchView;
import android.widget.TextView;

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

public class FragmentList extends Fragment {


    /*
     * constructor to inflate fragment
     */
    public FragmentList() {
        super(R.layout.layout_fragment_list);
    }

    @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);
        PriceListsAdapter myPriceListAdapter = new PriceListsAdapter();
        myRecyclerView.setAdapter(myPriceListAdapter);

        // https://codingwithmitch.com/blog/filtering-recyclerview-searchview/
        // SearchView
        SearchView mySearchView = view.findViewById(R.id.sv_text);
        // add listener for searchview
        mySearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String s) {
                myPriceListAdapter.getFilter().filter(s);
                return false;
            }

            @Override
            public boolean onQueryTextChange(String s) {
                myPriceListAdapter.getFilter().filter(s);
                return false;            }
        });

    }
}

MainActivity.java

package com.dedetok.capricelist;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;


public class MainActivity extends AppCompatActivity {

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

        // initialize database
        Log.e("dedetok", "MainActivity OnCreate Initilize database"); // debug
        DBCARepository.prepareDB(getApplicationContext());
        Log.e("dedetok", "MainActivity OnCreate Initilize database end"); // debug

        getSupportFragmentManager().beginTransaction().
                add(R.id.my_fragmentcontainerview, FragmentList.class, null).
                commit();

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.my_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId()==R.id.menu_add_pricelist) {
            // open fragment Detail for new PriceList
            getSupportFragmentManager().beginTransaction().
                    replace(R.id.my_fragmentcontainerview, FragmentDetail.class,null).
                    addToBackStack("List").
                    commit();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

NOTE: Some code may reference to other source code in comment (some has modified to make it run), do not remove urls if you wish to copy paste code and use the code.