Monday, February 16, 2026

Privacy Policy for Wreda Text Helper

 

Privacy Policy


Effective Date: 16 February 2026


Thank you for using Wreda Text Helper.


1. Overview


Wreda Text Helper is designed to assist users with text accessibility features. We respect your privacy and are committed to protecting it.


2. No Data Collection


Wreda Text Helper:


Does NOT collect any personal information


Does NOT collect usage data


Does NOT track users


Does NOT store any personal data



We do not collect names, emails, phone numbers, device identifiers, location data, or any other personal information.


3. No Internet Usage


This application:


Does NOT require an internet connection


Does NOT transmit any data to external servers


Works fully offline



All functionality runs locally on your device.


4. No Advertising


Wreda Text Helper:


Does NOT contain advertisements


Does NOT use AdMob or any third-party advertising services



5. No Data Sharing


Since we do not collect any data, we do not share, sell, rent, or distribute any user information to third parties.


6. Accessibility Service Usage


Wreda Text Helper uses Android Accessibility Service solely to provide its core functionality.


The service is used only to assist users with text-related features and does not collect, store, or transmit any user data.


7. Children’s Privacy


Wreda Text Helper does not knowingly collect any personal information from children or adults. The app is safe for general use.


8. Changes to This Privacy Policy


If this Privacy Policy is updated, changes will be reflected in the updated version of the app listing.


9. Contact


If you have any questions about this Privacy Policy, you may contact:


Developer: dedeetok

Email: dedetoke@gmail.com

 


 

Android java: local webview from local html e.g help page

Create directory and html files

Switch to Project View (not Android view)

  1. Go to: [your_project]
  2. app/src/main/
  3. Right click on main
  4. → New
  5. → Directory
  6. → select or type "assets"
  7. right click "assets" 
  8. → New
  9. → Directory
  10. → type "webhelp"
  11. right click "webhelp" 
  12. → New
  13. → File
  14. → type "htmlhelp.html"
  15. Repeat step 11 for your language e.g. id for Indonesia e.g. "htmlhelp_id.html" 

You can copy paste html code into htmlhelp.html" and "htmlhelp_id.html"

here is layout to show the webview

    ...
    <WebView
        android:id="@+id/my_web_help"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
    ... 

here is code to show the webview

...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
...
        WebView webView = view.findViewById(R.id.my_web_help);

        // Safe defaults
        webView.getSettings().setJavaScriptEnabled(false);
        webView.getSettings().setAllowFileAccess(true);
        webView.getSettings().setDomStorageEnabled(false);

        loadHelpPage(webView);
...
    }

    /*
     * file html helper
     */
    private void loadHelpPage(WebView webView) {

        String lang = Locale.getDefault().getLanguage();
        String fileName;

        if ("id".equals(lang)) {
            fileName = "htmlhelp_id.html";
        } else {
            fileName = "htmlhelp.html";
        }

        webView.loadUrl("file:///android_asset/webhelp/" + fileName);
    }
... 

Optional configuration webview when your application got trouble when submit to application store e.g google play store

WebSettings settings = webView.getSettings();

settings.setJavaScriptEnabled(false);
settings.setDomStorageEnabled(false);
settings.setAllowFileAccess(true);          // needed for assets
settings.setAllowContentAccess(false);
settings.setAllowFileAccessFromFileURLs(false);
settings.setAllowUniversalAccessFromFileURLs(false);
settings.setSupportZoom(false);
settings.setBuiltInZoomControls(false);

  

Friday, February 13, 2026

java: comparison between database api in java android and jdbc in java standard edition

Here is comparison between database api in java android and jdbc in java standard edition  

Operation

Java Android

Java SE (JDBC)

INSERT

Returns new row ID (long)

Returns affected rows (int)

UPDATE

Returns affected rows (int)

Same

DELETE

Returns affected rows (int)

Same                      

In java SE (JDBC) id can be retrive using jse ps.getGeneratedKeys() return long where ps is PreparedStatement.

 

Android java: notification pattern code

add permission in project 

AndroidManifest.xml

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

MainActivity.java 

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        checkPermissions();
    ....
    }



    /*
     * check permission to read contact and write shared folder Download
     */
    private void checkPermissions() {
        List<String> permissions = new ArrayList<>();

        // 1. Android 13+ Notification Permission
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissions.add(Manifest.permission.POST_NOTIFICATIONS);
        }

        // API require string convert List<String> to String[];
        requestPermissionLauncher.launch(permissions.toArray(new String[0]));
    }

    /*
     * callback permission request
     */
    private final ActivityResultLauncher<String[]> requestPermissionLauncher =
            registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
                // Handle Notification permission (Android 13+)
                boolean notificationsGranted = true;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    notificationsGranted = result.getOrDefault(Manifest.permission.POST_NOTIFICATIONS, false);
                }

            }); 

create MyNotificationService.java class

public class MyNotificationService {

    public static final String CHANNEL_ID = "id_string_channel"; // todo
    public static final int NOTIF_ID = 123456;  // todo

    /*
     * to send notification
     */
    public static void sendNotification(Context context, String title, String message) {
        NotificationManager notificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        NotificationCompat.Builder myNotifBuilder = getNotificationBuilder(context);
        myNotifBuilder
                .setContentTitle(title)
                .setContentText(message);

        notificationManager.notify(NOTIF_ID, myNotifBuilder.build());
    }

    /*
     * get notification for foreground
     */
    public static Notification getForegroundNotification(Context context) {
        Notification myNotif = getNotificationBuilder(context).build();
        return myNotif;
    }
    
    /*
     * Helper that returns the BUILDER so you can customize it before building
     */
    private static NotificationCompat.Builder getNotificationBuilder(Context context) {
        NotificationManager notificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
                NotificationChannel notifChannel = new NotificationChannel(
                        CHANNEL_ID,
                        "Background Sync Service",
                        NotificationManager.IMPORTANCE_LOW
                );
                notificationManager.createNotificationChannel(notifChannel);
            }
        }

        return new NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(android.R.drawable.stat_notify_sync)
                .setPriority(NotificationCompat.PRIORITY_LOW)
                .setCategory(Notification.CATEGORY_SERVICE);
        //.setOngoing(true); // can not swap away
    }

 

 

Thursday, February 12, 2026

Android java: fragment pattern code

Fragment

use just portrait for phone and w600 for tablet (bigger screen)   

for portrait (phone) 

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >
    
    <!-- Top TextView -->
    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/top_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/app_name"
            android:textSize="18sp"
            android:textStyle="bold"
            android:padding="12dp"
            android:gravity="start|center_vertical"
            android:background="#DDDDDD"
            android:layout_weight="1"
            />

        <androidx.appcompat.widget.AppCompatImageButton
            android:id="@+id/button_help"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="?android:attr/selectableItemBackground"
            android:src="@android:drawable/ic_menu_help" />
    </androidx.appcompat.widget.LinearLayoutCompat>
    
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/my_fragment_container_main"
        android:name="[Your java main fragment with full package name]"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        />

</androidx.appcompat.widget.LinearLayoutCompat>

for w600 tablet

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <!-- Top TextView -->
    <TextView
        android:id="@+id/top_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Your Title Here"
        android:textSize="18sp"
        android:textStyle="bold"
        android:padding="12dp"
        android:gravity="center"
        android:background="#DDDDDD" />

    <!-- Horizontal Content -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="3">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/my_fragment_container_left"
        android:name="[Your java left fragment with full package name]"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <View
        android:id="@+id/divider_line"
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:background="#000000" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/my_fragment_container_main"
        android:name="[Your java main fragment with full package name]"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2" />

    </LinearLayout>

</LinearLayout> 

Guideline for layout LinearLayout

  • orientation android:orientation="vertical" 
    android:layout_width="match_parent" 
    android:layout_height="0dp" 
    android:layout_weight="1"
  • orientation android:orientation="horizontal"
    android:weightSum="3" 
    android:layout_width="0dp" 
    android:layout_height="match_parent" android:layout_weight="1"  

in main activity on onCreate(Bundle savedInstanceState)

    public boolean isDualPane = false; // default is portrait

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        isDualPane = findViewById(R.id.my_fragment_container_left) != null;

        // R.id.main id for root portrait & w600

        AppCompatImageButton buttonHelp = findViewById(R.id.button_help);
        buttonHelp.setOnClickListener(buttonHelpListener);        

        // Load Fragments only if this is a fresh start (savedInstanceState == null)
        // This prevents overlapping fragments during screen rotation
        if (savedInstanceState == null) {

            // If system loaded the w600 layout, load both Fragment
            if (isDualPane) {
                replaceFragment(
                        R.id.my_fragment_container_left,
                        new FragmentLeft(),
                        null);
                replaceFragment(
                        R.id.my_fragment_container_main,
                        new FragmentMain(),
                        null);
            } else {
                // default load main container
                replaceFragment(
                        R.id.my_fragment_container_main,
                        new FragmentMain(),
                        null); // when navigating add "Fragment Main"
            }
        }

        View.OnClickListener buttonHelpListener = new View.OnClickListener() {
   
         @Override
       
     public void onClick(View view) {
           
     Fragment currentFragment =
               
         getSupportFragmentManager()
                   
             .findFragmentById(R.id.my_fragment_container_main);
                if (currentFragment instanceof FragmentMain) {
                    // NOTE: we still use R.id.my_fragment_container_main in portrait
  
                  replaceFragment(
                            R.id.my_fragment_container_main,
                            new FragmentLeft(),
                            "Main Menu");
                } else {
                    getSupportFragmentManager().popBackStack();
                }
            }
        };

        /*
         * fragment helper
         */
        private void replaceFragment(
                         int containerId,
                         Fragment fragment,
                         String addToBackStackString) {
            FragmentTransaction transaction =
                    getSupportFragmentManager().beginTransaction();
            transaction.replace(containerId, fragment);
            if (addToBackStackString!=null) {
                transaction.addToBackStack(addToBackStackString);
            }
            transaction.commit();
        }
        ....
To implement onbackpresseddispatcher pattern code, see this  https://dedetoknotes.blogspot.com/2026/01/android-java-onbackpresseddispatcher.html

 

Saturday, February 7, 2026

Debian 13: removing kernel left over after kernel upgrade

wifi and some device require compile in debian depend on kernel version. this documentation is intended as a small guide to tidy up your system.

list of ii and rc status of package

# uname -r
6.12.63+deb13-amd64
# dpkg -l | grep linux-image
rc  linux-image-6.1.0-33-amd64                         6.1.133-1                            amd64        Linux 6.1 for 64-bit PCs (signed)
rc  linux-image-6.1.0-34-amd64                         6.1.135-1                            amd64        Linux 6.1 for 64-bit PCs (signed)
rc  linux-image-6.1.0-37-amd64                         6.1.140-1                            amd64        Linux 6.1 for 64-bit PCs (signed)
rc  linux-image-6.1.0-9-amd64                          6.1.27-1                             amd64        Linux 6.1 for 64-bit PCs (signed)
rc  linux-image-6.12.38+deb13-amd64                    6.12.38-1                            amd64        Linux 6.12 for 64-bit PCs (signed)
rc  linux-image-6.12.38+deb13-amd64-unsigned           6.12.38-1                            amd64        Linux 6.12 for 64-bit PCs
rc  linux-image-6.12.41+deb13-amd64                    6.12.41-1                            amd64        Linux 6.12 for 64-bit PCs (signed)
rc  linux-image-6.12.41+deb13-amd64-unsigned           6.12.41-1                            amd64        Linux 6.12 for 64-bit PCs
rc  linux-image-6.12.43+deb13-amd64                    6.12.43-1                            amd64        Linux 6.12 for 64-bit PCs (signed)
rc  linux-image-6.12.43+deb13-amd64-unsigned           6.12.43-1                            amd64        Linux 6.12 for 64-bit PCs
rc  linux-image-6.12.48+deb13-amd64                    6.12.48-1                            amd64        Linux 6.12 for 64-bit PCs (signed)
rc  linux-image-6.12.48+deb13-amd64-unsigned           6.12.48-1                            amd64        Linux 6.12 for 64-bit PCs
rc  linux-image-6.12.57+deb13-amd64                    6.12.57-1                            amd64        Linux 6.12 for 64-bit PCs (signed)
ii  linux-image-6.12.57+deb13-amd64-unsigned           6.12.57-1                            amd64        Linux 6.12 for 64-bit PCs
ii  linux-image-6.12.63+deb13-amd64                    6.12.63-1                            amd64        Linux 6.12 for 64-bit PCs (signed)
ii  linux-image-amd64                                  6.12.63-1                            amd64        Linux for 64-bit PCs (meta-package)

Note about rc and ii 

  • rc = package removed, but config files remain
  • ii = desired state install and current state installed 

we can conclude these packages are not used anymore but still exist in our system:

  1. rc linux-image-6.1.0-*
  2. rc linux-image-6.12.38*
  3. rc linux-image-6.12.41*
  4. rc linux-image-6.12.43*
  5. rc linux-image-6.12.48*

We can remove those packages which is kernel left over, using this command:

# dpkg -l | awk '/^rc  linux-image/ {print $2}' | xargs dpkg --purge
(Reading database ... 180811 files and directories currently installed.)
Purging configuration files for linux-image-6.1.0-33-amd64 (6.1.133-1) ...
Purging configuration files for linux-image-6.1.0-34-amd64 (6.1.135-1) ...
Purging configuration files for linux-image-6.1.0-37-amd64 (6.1.140-1) ...
Purging configuration files for linux-image-6.1.0-9-amd64 (6.1.27-1) ...
dpkg: warning: while removing linux-image-6.1.0-9-amd64, directory '/lib/modules' not empty so not removed
Purging configuration files for linux-image-6.12.38+deb13-amd64 (6.12.38-1) ...
Purging configuration files for linux-image-6.12.38+deb13-amd64-unsigned (6.12.38-1) ...
rmdir: failed to remove '/lib/modules/6.12.38+deb13-amd64': No such file or directory
Purging configuration files for linux-image-6.12.41+deb13-amd64 (6.12.41-1) ...
Purging configuration files for linux-image-6.12.41+deb13-amd64-unsigned (6.12.41-1) ...
rmdir: failed to remove '/lib/modules/6.12.41+deb13-amd64': No such file or directory
Purging configuration files for linux-image-6.12.43+deb13-amd64 (6.12.43-1) ...
Purging configuration files for linux-image-6.12.43+deb13-amd64-unsigned (6.12.43-1) ...
rmdir: failed to remove '/lib/modules/6.12.43+deb13-amd64': No such file or directory
Purging configuration files for linux-image-6.12.48+deb13-amd64 (6.12.48-1) ...
Purging configuration files for linux-image-6.12.48+deb13-amd64-unsigned (6.12.48-1) ...
rmdir: failed to remove '/lib/modules/6.12.48+deb13-amd64': No such file or directory
Purging configuration files for linux-image-6.12.57+deb13-amd64 (6.12.57-1) ...
I: /vmlinuz is now a symlink to boot/vmlinuz-6.12.63+deb13-amd64
I: /initrd.img is now a symlink to boot/initrd.img-6.12.63+deb13-amd64
rmdir: failed to remove '/lib/modules/6.12.57+deb13-amd64': Directory not empty

 

 

Saturday, January 31, 2026

Debian 13: using unbound as DNS over https resolver in conjuction with NetworkManager and Systemd Resolved

Assume:

  • NetworkManager and Systemd-Resolved already work 

Install unbound

# apt-get install unbound

Edit /etc/systemd/resolved.conf and set:

[Resolve]
DNS=127.0.0.1:5335
Domains=~.

Make sure this line in /etc/unbound/unbound.conf

...
include-toplevel: "/etc/unbound/unbound.conf.d/*.conf"
...

Create and edit /etc/unbound/unbound.conf.d/google-doh.conf

server:
    # Port 5335 is safer for laptops than 5353 (mDNS)
    port: 5335
    interface: 127.0.0.1
    access-control: 127.0.0.0/8 allow
    
    # Required for TLS verification
    tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
forward-zone:
name: "."
    # Using DoT (Port 853) instead of DoH (Port 443)
    forward-tls-upstream: yes
    forward-addr: 8.8.8.8@853#dns.google
    forward-addr: 8.8.4.4@853#dns.google

Enable unbound and restart all services

# systemctl enable unbound
# systemctl start unbound
# systemctl restart systemd-resolved
# systemctl restart NetworkManager

Test

# resolvectl query google.com
google.com: 172.253.118.138
            172.253.118.102
            172.253.118.101
            172.253.118.113
            172.253.118.139
            172.253.118.100
            2404:6800:4003:c11::8a
            2404:6800:4003:c11::71
            2404:6800:4003:c11::64
            2404:6800:4003:c11::8b


# resolvectl query duckduckgo.com
duckduckgo.com: 20.43.161.105

-- Information acquired via protocol DNS in 54.3ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network


Thursday, January 29, 2026

Android java: write and delete file in folder shared folder e.g Download pattern code

Target device Android 7 (API 24) to Android 16 (API 36)

AndroidManifest.xml permission 

     <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    <!-- targeting api 13++ use media store -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

To write contact (vcard) to folder com.dedetok.wredacontactbackup in folder Download

    final static String subFolder = "com.dedetok.wredacontactbackup"; // folder ref
    private static File backupFolder, backupFile; //


    private static boolean writeSingleVcf(Context context, Collection<VCard> vCards) {
        try {

            String date = new SimpleDateFormat("yyyyMMddHHmmSS", Locale.US).format(new Date());
            String fileName = date + ".vcf";


            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                // ✅ API 29-36 (Android 10+): Use MediaStore (Scoped Storage)
                return MyWorker.writeMediaStore(context, fileName, vCards);
            } else {
                // ✅ API 24-28 (Android 7-9): Use traditional File API
                // ✅ API 29-36 (Android 10+): Use MediaStore (Scoped Storage)
                return MyWorker.writeLegacy(context, fileName, vCards);

            }
        } catch (Exception e) {
            //Log.e("dedetok",e.getMessage()); // debug
            return false;
        }
    }


    /*
     * write backup MediaStore
     */
    @RequiresApi(api = Build.VERSION_CODES.Q)
    private static boolean writeMediaStore (Context context, String fileName, Collection<VCard> vCards) throws IOException{
        ContentValues values = new ContentValues();
        //Log.e("dedetok", "writeMediaStore"); // debug
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
        values.put(MediaStore.MediaColumns.MIME_TYPE, "text/x-vcard");
        // Creates 'Download/YourSubFolder/' automatically
        values.put(MediaStore.MediaColumns.RELATIVE_PATH,
                Environment.DIRECTORY_DOWNLOADS + File.separator + subFolder);

        Uri uri = context.getContentResolver().insert(
                MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
        if (uri == null) return false;

        OutputStream os = context.getContentResolver().openOutputStream(uri);
        Ezvcard.write(vCards).version(VCardVersion.V3_0).go(os);
        return true;
    }

    /*
     * write backup in legacy mode
     */
    private static boolean writeLegacy(Context context, String fileName, Collection<VCard> vCards) throws IOException{
        // Setup Directory
        File downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        backupFolder = new File(downloads, MySharePreferences.PREF_NAME);
        // Create folder if missing
        if (!backupFolder.exists()) {
            boolean success = backupFolder.mkdirs();
            if (!success) {
                //Log.e("dedetok", "Failed to create folder. Check permissions!"); // debug
                return false;
            }
        }

        backupFile = new File(backupFolder, fileName);
        try (FileOutputStream fos = new FileOutputStream(backupFile)) {
            Ezvcard.write(vCards).version(VCardVersion.V3_0).go(fos);
        }

        //Log.d("dedetok", "File saved at: " + backupFolder.getName()); // debug

        // Tell Android to scan the file so it shows up in Windows/File Manager
        MediaScannerConnection.scanFile(
                context,
                new String[]{ backupFile.getAbsolutePath() },
                null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String path, Uri uri) {
                        //Log.e("dedetok", "Scanned: " + path);
                    }
                }
        );

        //Log.e("dedetok", "File created successfully "+backupFile.getName()); // debug
        return true;

    }

To delete file created by application from folder com.dedetok.wredacontactbackup in folder Download

    /*
     * delete File helper for android 7 yo 16
     */
    static private boolean deleteFile(Context context, File fileToDelete) {
        boolean returnValue = false;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // Android 7–9
            if (fileToDelete.exists()) {
                returnValue = fileToDelete.delete();
                if (returnValue) {
                    // IMPORTANT: Tell the system the file is gone so Google Files updates
                    MediaScannerConnection.scanFile(context, new String[]{fileToDelete.getAbsolutePath()}, null, null);
                }
            }
        } else {
            // Android 10 to 16
            ContentResolver resolver = context.getContentResolver();
            Uri collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI;

            // IMPORTANT: RELATIVE_PATH must end with a slash /
            String relativePath = Environment.DIRECTORY_DOWNLOADS + "/" + subFolder + "/";

            String selection = MediaStore.MediaColumns.DISPLAY_NAME + "=? AND " +
                    MediaStore.MediaColumns.RELATIVE_PATH + "=?";

            String[] selectionArgs = new String[]{
                    fileToDelete.getName(),
                    relativePath
            };

            int rows = 0;
            try {
                // Directly delete from MediaStore - this usually deletes the physical file too
                rows = resolver.delete(collection, selection, selectionArgs);
            } catch (Exception e) {
                //Log.e("dedetok", "MediaStore delete failed", e);
            }

            if (rows > 0) {
                returnValue = true;
            } else {
                // FALLBACK: Physical delete if MediaStore didn't find it
                if (fileToDelete.exists()) {
                    returnValue = fileToDelete.delete();
                    if (returnValue) {
                        // Sync the index
                        MediaScannerConnection.scanFile(context, new String[]{fileToDelete.getAbsolutePath()}, null, null);
                    }
                }
            }
        }
        return returnValue;
    }

 

Android java: OnBackPressedDispatcher pattern code

AndroidManifest.xml 

    <application
...
        android:enableOnBackInvokedCallback="true" 

Main Activity 

On main activity declare:

        // callback on back key pressed
        OnBackPressedCallback myBackPressedCallback = new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {

                FragmentManager fm = getSupportFragmentManager();

                // Pop fragment back stack if possible
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    return;
                }

                // 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();
            }
        };

onCreate() in main activity

    ...
        // onbackkeypress
        // onBackPressed() deprecated -> OnBackPressedCallback & getOnBackPressedDispatcher()
        // dispatcher
        OnBackPressedDispatcher myOnbackPressedDispatcher = getOnBackPressedDispatcher();

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

Fragment

To replace fragment in main activity

               Fragment helpFragment = new FragmentHelp();

                requireActivity()
                        .getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.fragment_container, helpFragment)
                        .addToBackStack("help")
                        .commit();

addToBackStack("help") is the way android system implement on back pressed

If you have button back on your fragment e.g helpFragment, you need to emulate OnBackPressedDispatcher

                requireActivity()
                        .getOnBackPressedDispatcher()
                        .onBackPressed();

Activity B

onCreate in activity B

    // Inside Activity B's onCreate
    getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
        // This runs when the physical back button OR 
        // the programmatic onBackPressed() is called.
        finish(); 
        }
    });

    // Inside your Button's onClick (Assuming it's in the Activity)
    buttonExit.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
        // This triggers the callback above
        getOnBackPressedDispatcher().onBackPressed();
        }
    });

 

 

Privacy Policy for Wreda Contact Backup

 

Wreda Contact Backup

This application will help you to backup your contact offline. These application require permission:

  1. Read Contact
  2. Write Storage
  3. Notification

No internet connection is require since the backup will store at Download folder with sub folder com.dedetok.wredacontactbackup folder. Maximum files are set to 60 to avoid fulling your phone get full.

Minimum Android supported is 7.0.

How to use

You can backup directly by pressing "Backup Now"

For Android supported you can make scheduled. The options are:

  • Weekly
  • Monthly

We don't provide cloud backup, you need to safe your backup file (vcf) manually in your trusted provider by uploading the vcf file.

Data Privacy Policy

The Application does not collect, store, or share any personal information from its users.

  • No Personal Data: We do not collect names, email addresses, phone numbers, or physical addresses.
  • No Device Identifiers: The application does not access your device's unique ID, IP address, or contacts.
  • No Location Tracking: We do not track or collect real-time geographical location information.

The Application does not use third-party services (such as analytics or advertising SDKs) that collect data for identification or tracking.

Since the Application does not collect or store any user data, there is no risk of your personal data being accessed by unauthorized individuals through our servers.

If you have any questions or suggestions about this Privacy Policy, do not hesitate to contact us at dedetoke@gmail.com.

 


 

 

Wednesday, January 28, 2026

CodeIgniter: Tutorial rest API by chatgpt

Table name :tmp_harga_jual

    id Primary   int(11) 
    nama             varchar(100) 
    satuan           varchar(20) 
    harga             decimal(10,2)     
    kode_modal varchar(20)

1. create Model app/Models/TmpHargaJualModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class TmpHargaJualModel extends Model
{
    protected $table      = 'tmp_harga_jual';
    protected $primaryKey = 'id';

    protected $allowedFields = [
        'nama',
        'satuan',
        'harga',
        'kode_modal'
    ];

    protected $returnType = 'array';
}

2. create Controller app/Controllers/TmpHargaJual.php

<?php

namespace App\Controllers;

use App\Models\TmpHargaJualModel;
use CodeIgniter\RESTful\ResourceController;

class TmpHargaJual extends ResourceController
{
    protected $modelName = TmpHargaJualModel::class;
    protected $format    = 'json';

    public function index()
    {
        $data = $this->model->findAll();

        return $this->respond([
            'status' => 'success',
            'data'   => $data
        ]);
    }
}

3. Add route app/Config/Routes.php

<?php

use CodeIgniter\Router\RouteCollection;

/**
 * @var RouteCollection $routes
 */
$routes->get('/', 'Home::index');

$routes->get('hello', 'Hello::index');

$routes->get('harga-jual', 'TmpHargaJual::index');

Add search

modify app/Controllers/TmpHargaJual.php

<?php

namespace App\Controllers;

use App\Models\TmpHargaJualModel;
use CodeIgniter\RESTful\ResourceController;

class TmpHargaJual extends ResourceController
{
    protected $modelName = TmpHargaJualModel::class;
    protected $format    = 'json';

    public function index()
    {
        $nama = $this->request->getGet('nama');

        if ($nama) {
            $data = $this->model
                ->like('nama', $nama)
                ->findAll();
        } else {
            $data = $this->model->findAll();
        }

        return $this->respond([
            'status' => 'success',
            'count'  => count($data),
            'data'   => $data
        ]);
    }
}

Test using curl 

$ curl "http://localhost/myapp/harga-jual?nama=kopi

 

Common functions:

| HTTP   | URL             | Meaning  |
| ------ | --------------- | -------- |
| GET    | `/harga-jual`   | List all |
| GET    | `/harga-jual/1` | Get one  |
| POST   | `/harga-jual`   | Create   |
| PUT    | `/harga-jual/1` | Update   |
| DELETE | `/harga-jual/1` | Delete   |

 

 

Friday, January 23, 2026

Android java: creating icon launcher and icon for Play Store

Prepare your file, it preferred to use transparent background. I use png for image source. 

Open your Android Studio, follow these steps: 

Step 1 open Resource Manager: 

Tool -> Resource Manager

Press "+" -> Image Asset it will open Configure Image Asset

Fill

  • Icon type: Launcer icons (Adaptive and Legacy)
  • Name: my_ic_launcher 

Step 2 Set foreground with your png file:

Tab: Foreground Layer

  • Source Asset
  • Asset type: Image
    Path: /Workspace/[your_project]/iconLauncher.png
  • Scalling
    Trim: Yes for automatic scalling
    If Trim No, you can adjust scaling by scrolling Resize until it doesn't get cuted.

Step 3 set background: 

Tab: Background Layer
Asset type:
Color: directing choose color (recommended)
Image: xml file to edit or put image for background

Step 4: Finish

Attach your icon into your application, open your AndroidManifest.xml

...
    <application
...
        android:roundIcon="@mipmap/my_ic_launcher"
...

Icon for playstore can found at Workspace/[your_project]/app/src/main/my_ic_launcher-playstore.png 

Android java: gradle corrupted, how to fix it

This is error message when gradle corrupted due to accidentally deleted or fail updated.

"The contents of the immutable workspace '~/.gradle/caches/9.1.0/groovy-dsl/52b1078aa736f3877f522d2046a7f837' have been modified" 

To make easier to fix gradle, exit Android Studio, open shell and go to project root directory. You will find file gradlew.

Clean up your gradle

$ ./gradlew --stop
Stopping Daemon(s)
2 Daemons stopped
$ rm -rf ~/.gradle/caches/9.1.0/

Run help to force gradlew download and rebuild library required.

$ ./gradlew help
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

> Configure project :app
WARNING: The option setting 'android.usesSdkInManifest.disallowed=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.sdk.defaultTargetSdkToCompileSdkIfUnset=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.enableAppCompileTimeRClass=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.builtInKotlin=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.newDsl=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.r8.optimizedResourceShrinking=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.defaults.buildfeatures.resvalues=true' is deprecated.
The current default is 'false'.
It will be removed in version 10.0 of the Android Gradle plugin.
w: file://~/Workspace/Android/WredaContactBackup/app/build.gradle.kts:5:1: 'fun Project.android(configure: Action<BaseAppModuleExtension>): Unit' is deprecated. Replaced by com.android.build.api.dsl.ApplicationExtension.
This class is not used for the public extensions in AGP when android.newDsl=true, which is the default in AGP 9.0, and will be removed in AGP 10.0.
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties

> Task :help

Welcome to Gradle 9.1.0.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see more detail about a task, run gradlew help --task <task>

To see a list of command-line options, run gradlew --help

For more detail on using Gradle, see https://docs.gradle.org/9.1.0/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 28s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.1.0/userguide/configuration_cache_enabling.html

Open your Android Studio, repair android studio project choose index repair.

If this error occurred

"Ambiguous method call: both 'FragmentActivity.onCreate(Bundle) (In ~/.gradle/caches/9.1.0/transforms/dae60150ff0476652c6b13819aa96f10/transformed/fragment-1.5.4/jars/classes.jar!/androidx/fragment/app/FragmentActivity.class)' and 'ComponentActivity.onCreate(Bundle) (In ~/.gradle/caches/9.1.0/transforms/48c7a661e0aa50fff79a603bd45cc34c/transformed/activity-1.12.2/jars/classes.jar!/androidx/activity/ComponentActivity.class)' match"

Invalidate cache (everything) and restart.

If you find some deprecated gradle option like these:

$ ./gradlew help
Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --status for details

> Configure project :app
WARNING: The option setting 'android.usesSdkInManifest.disallowed=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.sdk.defaultTargetSdkToCompileSdkIfUnset=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.enableAppCompileTimeRClass=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.builtInKotlin=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.newDsl=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.r8.optimizedResourceShrinking=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.defaults.buildfeatures.resvalues=true' is deprecated.
The current default is 'false'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties

> Task :help

Welcome to Gradle 9.1.0.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see more detail about a task, run gradlew help --task <task>

To see a list of command-line options, run gradlew --help

For more detail on using Gradle, see https://docs.gradle.org/9.1.0/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 5s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.1.0/userguide/configuration_cache_enabling.html

Edit file gradle.properties, find those configuration and take some adjustment. You can close Android Studio and back to shell, this is my gradle.properties:

# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
#android.defaults.buildfeatures.resvalues=true #deprecated
#android.sdk.defaultTargetSdkToCompileSdkIfUnset=false #deprecated
#android.enableAppCompileTimeRClass=false #deprecated
#android.usesSdkInManifest.disallowed=false #deprecated
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
#android.r8.optimizedResourceShrinking=false #deprecated
#android.builtInKotlin=false #deprecated
#android.newDsl=false #deprecated
# edited dedetok
#android.enableJetifier=true
android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false

Validate gradle is clear

$ ./gradlew help

> Task :help

Welcome to Gradle 9.1.0.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see more detail about a task, run gradlew help --task <task>

To see a list of command-line options, run gradlew --help

For more detail on using Gradle, see https://docs.gradle.org/9.1.0/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 503ms
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.1.0/userguide/configuration_cache_enabling.html

Now you have clean project.  

Saturday, January 17, 2026

adb command references (android debug bridge)

to add path for platform-tool edit .bashrc (for Debian), append at the end. log out and sign in again to get affect or run $ source ~/.bashrc

export PATH=$PATH:$HOME/AndroidStudio/Sdk/platform-tools/

to show devices attached 

$ adb devices
* daemon not running; starting now at tcp:5037
adb * daemon started successfully
List of devices attached
A6501D9863TH039779    unauthorized -> Not allow, please allow on your device
$ adb devices
List of devices attached
A6501D9863TH039779    device -> your device allow to debug

to install application (don't support aab, compile to apk) 

$ adb install example.apk
Performing Streamed Install
Success

to uninstall package 

$ adb uninstall com.dedetok.radiowalkman
Success

to uninstall package (bloatware) for user 0 (default user). Use adb shell pm instead directly adb uninstall. Note: it just remove for current default user, it does not remove from firmware, you need to root your device for fully removal. After reset your device, the bloatware will come again.

$ adb uninstall --user 0 com.example.app vs 
$ adb shell uninstall --user 0 com.example.app
$ adb shell pm uninstall --user 0 net.bat.store
Success
com.transsion.aivoiceassistant

to list packages 

$ adb shell pm list packages | grep dedetok
package:com.dedetok.turuntirta
package:com.dedetok.pitrapuja

to list directory Download 

$ adb shell ls /sdcard/Download/com.dedetok.wredacontactbackup
test.txt

to get error Logcat 

$ adb logcat | grep com.dedetok.wredacontactbackup

  • WorkManager
    $ adb logcat -s dedetok WorkManager WM-WorkerWrapper
    --------- beginning of system
    --------- beginning of crash
    --------- beginning of main
    01-20 14:51:59.303 25534 25561 E WM-WorkerWrapper: Didn't find WorkSpec for id 6548d3d5-2c2e-47a0-a795-ff4f9365b2be
    01-20 14:51:59.311 25534 25561 E WM-WorkerWrapper: Didn't find WorkSpec for id e42ac4be-f95e-41e8-9694-e4eb6bc40671
    01-20 14:51:59.319 25534 25561 E WM-WorkerWrapper: Didn't find WorkSpec for id e42ac4be-f95e-41e8-9694-e4eb6bc40671

to get error

$ adb shell dumpsys dropbox | grep com.dedetok.wredacontactbackup

to clear data include cache for package com.dedetok.wredacontactbackup

$ adb shell pm clear com.dedetok.wredacontactbackup

Check JobScheduler (where WorkManager stores jobs)

$ adb shell dumpsys jobscheduler | grep -A 30 "com.dedetok.wredacontactbackup"
  JOB #u0a124/1: 7b2babf com.dedetok.wredacontactbackup/androidx.work.impl.background.systemjob.SystemJobService
    u0a124 tag=*job*/com.dedetok.wredacontactbackup/androidx.work.impl.background.systemjob.SystemJobService

#u0a124/1 last digit /1 is job id  

Force scheduler to run, use job id

$ adb shell cmd jobscheduler run -f com.dedetok.wredacontactbackup 1
Running job [FORCED] 

 

 

 

 

Android java: dealing with Trusted credentials on Android 7.1.1 below for letencrypt

Dealing with connection to https on old android may depend on Trusted credentials on the the device. 

Letsencrypt root certificate does not installed on Trusted credentials prior android 7.1.1.

Some application may throw:

java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

Solution 1 add user Trusted credentials 

Install Letsencrypt pem manually on old phone i.e android version 7.1.1 or older. You can download from https://letsencrypt.org/certs/isrgrootx1.pem?hl=en-US .

On your device (mine is evercoss gen pro x pro android 7.0). Go to setting -> Security.

You may find:

  • Trusted Credentials 
  • User Credentials
  • Install from SD Card

Choose "Install from SD Card":

  • Filed Name of Certification e.g ISGR ROOT X1 or Letsecrypt.
  • Credentials Use select VPN and aps

Open your Android Studio project and create res/xml/network_security_config.xml.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </base-config>
</network-security-config>

Edit AndroidManifest.xml

...
   <application
    ... 
   android:networkSecurityConfig="@xml/network_security_config"
   ...
   >
...

If https server ever falls back to http (not https), you should add cleartextTrafficPermitted="true" to the <base-config> tag (Not Recommended except your application need to access http):

...
<base-config 
   ...
   cleartextTrafficPermitted="true"
   ...
>
...

if this XML fix doesn't work, it's not the certificate—it's the Android 7.0 Cipher bug. In that specific case, you will have to use Conscrypt library (Solution 2).

Solution 2 using Conscript Library

Add dependency into gradle.build app

dependencies {
    ...
    implementation 'org.conscrypt:conscrypt-android:2.5.2'
    ...
}

Initialize at application startup or foreground

...
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { 
    // Below Android 7.1.1
    Security.insertProviderAt(Conscrypt.newProvider(), 1);
}
...


Tuesday, January 13, 2026

Android java: input events processing (collection)

There 2 ways to handle input events from user input

  1. Immediate: using listener
  2. At process: at the end to process data, usually user fire "process" button 

Button

    @Override
    protected void onCreate(Bundle savedInstanceState) {
...
        // to process
        AppCompatButton buttonTest = findViewById(R.id.button_test); // change to your button id from layout
        buttonTest.setOnClickListener(myTest);
    }

    View.OnClickListener myButton = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // todo

        }
    };

TimePicker

1. Immediate 

...
        //on view created
        // date picker
        TimePicker myTimePicker = view.findViewById(R.id.my_time_picker);
        myTimePicker.setOnTimeChangedListener(timeChangeListener);
...
    // TimePicker Listeneer
    private final TimePicker.OnTimeChangedListener timeChangeListener =
            new TimePicker.OnTimeChangedListener() {
        @Override
        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
            // TODO
        }
    };

2.  At process

    // Pull the values exactly as they are right now
    int hour = timePicker.getHour();
    int minute = timePicker.getMinute();

RadioGroup

1. Immediate

...
        //on view created
        // date picker
        RadioGroup radioSelectModeBackup = view.findViewById(R.id.radio_select_mode_backup);
        radioSelectModeBackup.setOnCheckedChangeListener(radioListener);
...
    // RadioGroup Listener
    RadioGroup.OnCheckedChangeListener radioListener = new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(@NonNull RadioGroup radioGroup, int idSelected) {
            if (idSelected==R.id.radio_weekly) {
                // weekly
            } else if (idSelected==R.id.radio_moonthly) {
                // monthly
            }
        }
    };

2. At process 

    // Inside your "Save" or "Submit" button click listener
    int selectedId = radioSelectModeBackup.getCheckedRadioButtonId();

    if (selectedId == R.id.radio_weekly) {
        // Logic for weekly backup
    } else if (selectedId == R.id.radio_moonthly) {
        // Logic for monthly backup
    } else {
        // Nothing is selected (returns -1 if no default is set in XML)
    }

RecyclerView

RecyclerView layout

<?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="64dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:background="#FFFFFF"
    android:gravity="center_vertical">

    <HorizontalScrollView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:scrollbars="none"
        android:fillViewport="true">

        <TextView
            android:id="@+id/my_item_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:paddingStart="12dp"
            android:paddingEnd="12dp"
            android:textSize="20sp"
            android:maxLines="1"
            android:textColor="#333333" />
    </HorizontalScrollView>

    <ImageButton
        android:id="@+id/btn_add_phrase"
        android:layout_width="64dp"
        android:layout_height="match_parent"
        android:src="@android:drawable/ic_input_add"
        android:background="?android:attr/selectableItemBackground"
        android:contentDescription="Add phrase"
        android:scaleType="centerInside"
        app:tint="#4CAF50" />

</LinearLayout>

RecyclerView Adapter and Holder

public class MyRecyclerView extends RecyclerView.Adapter<MyRecyclerView.ViewHolder> {

    String[] stringsTmp = {"Hello", "Test", "How are you?", "Help me write", "The quick brown fox jumps over the lazy dog\n The quick brown fox jumps over the lazy dog"};
    OnMyItemClickListener onMyItemClickListener;

    public MyRecyclerView(OnMyItemClickListener listener) {
        // Use the parameter 'listener', not the uninitialized global variable
        this.onMyItemClickListener = listener;
    }

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

    @Override
    public void onBindViewHolder(@NonNull MyRecyclerView.ViewHolder holder, int position) {
        // Get the string from your array and put it in the TextView
        String text = stringsTmp[position];
        holder.textView.setText(text);

        // Add a click listener for the item NOT WORKINGN HERE
        // Set the click here. It can see 'onMyItemClickListener' and 'text'
        holder.addButton.setOnClickListener(v -> {
            Log.e("dedetok", "onBindViewHolder Clicked : " + text);
            if (onMyItemClickListener != null) {
                onMyItemClickListener.getString(text);
            }
        });

    }

    @Override
    public int getItemCount() {
        return stringsTmp.length;
    }


    /*
     * View Holder
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;
        public ImageButton addButton; // Changed from Button

        public ViewHolder(View view) {
            super(view);
            // Matches the ID in your item_phrase.xml
            textView = view.findViewById(R.id.my_item_view);
            addButton = view.findViewById(R.id.btn_add_phrase);

        }
    }

    public interface OnMyItemClickListener {
        void getString(String stringSelect);
    }
}

If you use action listener on item in holder, DO NOT add onItemSelected event into adapter or any else touch event. They will conflict with other event inside the items.

You need to implement interface  MyRecyclerView.OnMyItemClickListener on your activity or fragment.

You can remove all event on holder if you prefered to use onItemSelected event.