Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Thursday, February 26, 2026

Android java: using sqliteopenhelper pattern code

Using additional Room may increase size of application, means more lines required to executed. implement SQLiteOpenHelper rather then using Room Library, will give you more controll data for performance.
Please note that: 

  • deleting column require minimum sqlite version 3.35.0
  • every android vendor may use different version of sqlite, it is possible uses lower then sqlite version 3.35.0 

Suppose you have application that has published and install in world wide, with sqlite database 

version 1 

  1. id (auto increment)
  2. name

version 2

  1. id (auto increment)
  2. name
  3. datebirth

version 3

  1. id (auto increment)
  2. name
  3. datebirth
  4. address

The code

public class MySQLHelper extends SQLiteOpenHelper {

    private static final String dbName = "[your_database_file_name].db";
    private static final int DATABASE_VERSION = 3; // MUST HAVE


    /*
     * constructor
     */
    public MySQLHelper(@Nullable Context context) {
        // name String: of the database file, or null for an in-memory database
        super(context, dbName, null, DATABASE_VERSION);
    }

    /*
     * only call once when database not exist 
     */
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        createDB(sqLiteDatabase);
    }

    /*
     * use latest latest database structure
     */
    private void createDB(SQLiteDatabase sqLiteDatabase) {
        String CREATE_TABLE = "CREATE TABLE user (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "name TEXT, " +
                "datebirth TEXT, " +
                "address TEXT)";
        db.execSQL(CREATE_TABLE);
    }

    /*
     * only call when database exist 
     */
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int curVersion, int newVersion) {
        if (oldVersion < 2) {
            db.execSQL("ALTER TABLE user ADD COLUMN datebirth TEXT");
        }

        if (oldVersion < 3) {
            db.execSQL("ALTER TABLE user ADD COLUMN address TEXT");
        }
    }

    /*
     * only call when database exist
     * delete / drop column only available to sqlite 3.35.0 or higher
     * it will crash run on lower sqlite version
     * NOT RECOMMENDED TO USE DOWNGRADE
     */
    @Override
    public void onDowngrade(SQLiteDatabase sqLiteDatabase, int curVersion, int newVersion) {
        ...
    }

if you wish to drop column on device with sqlite lower then 3.35.0 use rename table, create table, copy values and drop table. Don't use lower DATABASE_VERSION, just increase version and remove unused column using onUpgrade. Here is the example code onUpgrade() to remove column:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) {
        db.execSQL("ALTER TABLE my_table ADD COLUMN datebirth TEXT");
    }
    if (oldVersion < 3) {
        db.execSQL("ALTER TABLE my_table ADD COLUMN address TEXT");
    }
    if (oldVersion < 4) {
        // --- STEP 1: Rename current table ---
        db.execSQL("ALTER TABLE my_table RENAME TO temp_table");

        // --- STEP 2: Create new table without 'address' ---
        db.execSQL("CREATE TABLE my_table ("
                + "id INTEGER PRIMARY KEY,"
                + "name TEXT,"
                + "datebirth TEXT)");

        // --- STEP 3: Copy data (only the columns you want to keep) ---
        db.execSQL("INSERT INTO my_table (id, name, datebirth) " +
                   "SELECT id, name, datebirth FROM temp_table");

        // --- STEP 4: Remove the old table ---
        db.execSQL("DROP TABLE temp_table");
    }

 

Monday, February 23, 2026

Android java: boot on boot receiver

This is a table on all android boot where on receive event trigger your application:

State Security Status getMySharedPreferences Result Download/ Folder Access
1. SIM PIN OS Kernel is paused Receiver won't run. Blocked.
2. Pattern/PIN Direct Boot Mode Success! (With your new code) Fail. (Shared Storage is still encrypted)
3. No Security Full Boot Success! Success!
4. SIM + Pattern Double Lock Receiver won't run. Blocked.    

Base of state of boot above, here is onReceive() skeleton code

public class MyBootReceiver extends BroadcastReceiver {

    // simple state to prevent double running
    private static boolean isRunning = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        // state of boot device
        String action = intent.getAction();
        if (action == null) return;

        switch (action) {
            case Intent.ACTION_LOCKED_BOOT_COMPLETED:
                // STAGE 1: Phone just turned on, PIN screen is visible.
                // DO: Check SharedPreferences (Device Protected) only.
                break;

            case Intent.ACTION_USER_UNLOCKED:
                // STAGE 2: User just entered PIN.
                // DO: Access Contacts and Folders now.
                break;

            case Intent.ACTION_BOOT_COMPLETED:
                // STAGE 3: System is fully initialized.
                if (isRunning) return; // Prevent double execution
        
        isRunning = true;
                // DO: Final cleanup or scheduling.
                break;

            case "android.intent.action.ALARM_MATCHED": // Your custom alarm action
                // DO: The actual backup if triggered by AlarmManager.
                break;
        }

 Permission and service for on boot receiver in android manifest 

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

...
    <application
...
        <receiver
            android:name=".MyBootReceiver"
            android:enabled="true"
            android:exported="true"
            android:directBootAware="true"
            >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
                <action android:name="android.intent.action.USER_UNLOCKED" />
            </intent-filter>
        </receiver>
...

Recommended boot receiver (gemini ai)

public class MyBootReceiver extends BroadcastReceiver {

    // simple state to prevent double running
    private static boolean isRunning = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        // state of boot device
        String action = intent.getAction();
        if (action == null) return;

        switch (action) {
            case Intent.ACTION_LOCKED_BOOT_COMPLETED:
                // Phone is locked. Use Device Protected Storage to schedule the next alarm.
                // Do NOT try to read contacts or write files here.

                break;

            case Intent.ACTION_USER_UNLOCKED:
            case Intent.ACTION_BOOT_COMPLETED:
                // FULL BOOT / UNLOCKED.
                if (isRunning) return; // Prevent double execution
        
        isRunning = true;
                // This is where you run your overdue check logic.
                break;

            case "android.intent.action.ALARM_MATCHED":
                // DO something, it is the time
                break;
        }


    }

 

 

 

Android java: Work Manager and Alarm Manager

Java Android Scheduling: Work Manager and Alarm Manager

WorkManager: 

  • Constraints: You can specify conditions like "only run when charging" or "only on Wi-Fi".
  • Persistence: Tasks survive device reboots and app crashes.
  • Backward Compatibility: It automatically chooses the best underlying API (JobScheduler, AlarmManager, etc.) based on the device's API level.
  • Limitation: It does not guarantee exact timing. The system may delay execution to optimize battery life. 

AlarmManager

  • Precision: Can wake the device from Doze mode to trigger a notification exactly when scheduled.
  • Lifecycle Independent: Operates outside your app's lifecycle once set.
  • It is resource-intensive because it wakes the device.
  • It does not support execution constraints (like network requirements).
  • For long-running tasks triggered by an alarm, Google recommends handing off the work to WorkManager from the alarm's BroadcastReceiver.  
Feature  WorkManager AlarmManager
Timing Deferrable (inexact) Precise (exact)
Guaranteed? Yes, even after reboot Not inherently (must reset on reboot)
Constraints Battery, Network, Storage None (time-only)
Power Efficiency High (optimized by OS) Low (wakes device)
Minimum Interval 15 minutes for periodic work None (can be immediate)
Supported Version Known stable on Android 13+ All

 

 

 

Wednesday, February 18, 2026

Java Netbeans: java with maven vs java with gradle

Comparison

Feature  Java with Maven Java with Gradle
Configuration Uses XML (pom.xml). Uses Groovy or Kotlin DSL (build.gradle).
Philosophy Convention over configuration; strict, linear lifecycle. Flexibility; task-oriented and highly customizable.
Performance Slower; typically lacks advanced incremental build tracking. Faster; uses build caching, daemons, and incremental compilation.
Learning Curve Easy for beginners due to standardized structure. Steeper; requires understanding the DSL and build logic.
IDE Integration Deeply integrated; NetBeans was a pioneer in native Maven support. Fully supported; provides fast project synchronization.
Start with default template Yes No

 

 

When Java with Maven 

  1.     Onboarding & Stability: Because Maven follows strict convention-over-configuration, any Java developer can join a project and immediately know where everything is and how to run it.
  2.     Predictability: Maven’s XML is declarative and "logic-free," making it very reliable for CI/CD pipelines where "zero-surprise" builds are critical.
  3.     Low Maintenance: Maven projects from 10 years ago often still build perfectly today. Gradle’s high-speed evolution means its build scripts can sometimes require updates when you upgrade versions.
  4.     Small Projects: For a single-module project, the speed difference is negligible. In these cases, the extra complexity and learning curve of Gradle may not be worth it. 

When Java with Gradle

  1.     Large, Multi-Module Projects: Gradle is significantly faster (sometimes 100x faster) because it only recompiles what changed and can share build results between different developers via a Build Cache.
  2.     Custom Workflows: If you need to do something non-standard (like custom file manipulations or complex deployment steps), Gradle’s Groovy/Kotlin DSL is much more powerful than writing custom Maven plugins.
  3.     Android Development: Gradle is the official build tool for Android, making it the only real choice for that ecosystem.

This content is created from AI chat. 

Monday, February 16, 2026

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 = "wredatexthelper"; //
    public static final int NOTIF_ID = 1846496091;  // crc from wredatexthelper

    /*
     * to send notification directly
     */
    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());
    }

    /*
     * 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
    }

    /*
     * get notification for foreground called from worker
     */
    public static Notification getForegroundNotification(Context context) {
        Notification myNotif = getNotificationBuilder(context).build();
        return myNotif;
    }
}

 

 

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"
    android:minHeight="48dp"
    >
    
    <!-- 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"?>
<
androidx.appcompat.widget.LinearLayoutCompat
    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 -->
    <
androidx.appcompat.widget.AppCompatTextView
        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 -->
    <
androidx.appcompat.widget.LinearLayoutCompat
        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" />

    </
androidx.appcompat.widget.LinearLayoutCompat>

</
androidx.appcompat.widget.LinearLayoutCompat

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
    
AppCompatImageButton buttonHelp;

    @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

        if (!isDualPane) {
            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");
                    buttonHelp.setImageResource(android.R.drawable.ic_menu_revert);
                } else {
                    getSupportFragmentManager().popBackStack();
                    buttonHelp.setImageResource(android.R.drawable.ic_menu_help);
                }
            }
        };

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

 

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 myAlertDialog = new AlertDialog.Builder(
                        MainActivity.this);
                myAlertDialog.setTitle(R.string.dialog_title).
                        setMessage(R.string.dialog_message).
                        setIcon(android.R.drawable.ic_dialog_alert).
                        setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                // finish() will execute onDestroy()
                                finish();
                            }
                        }).
                        setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                // do nothing
                            }
                        }).
                        show();
            }
        };

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

 

 

Friday, January 23, 2026

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

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.

 

 

 

 

 

Wednesday, January 7, 2026

Java: using build tool gradle wrapper to setup project and run

 

  • java application openjdk 17
  • gradle 8.7

install gradle 

# apt-get install gradle 

create folder project example

$ mkdir example
$ cd example

create init project

wrap gradle version for your project 

$ gradle wrapper --gradle-version 8.7
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (build 17.0.16+8-Debian-1deb12u1)
OpenJDK 64-Bit Server VM (build 17.0.16+8-Debian-1deb12u1, mixed mode, sharing)

init using gradle 8.7  debian 13 uses old 4.4 

$ ./gradlew init \
  --type java-application \
  --dsl groovy \
  --test-framework junit-jupiter \
  --package com.dedetok \
  --project-name example
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (build 17.0.16+8-Debian-1deb12u1)
OpenJDK 64-Bit Server VM (build 17.0.16+8-Debian-1deb12u1, mixed mode, sharing)
Downloading https://services.gradle.org/distributions/gradle-8.7-bin.zip
...............................................................................................................................
Unzipping ~/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu/gradle-8.7-bin.zip to ~/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu
Set executable permissions for: ~/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu/gradle-8.7/bin/gradle

Welcome to Gradle 8.7!

Here are the highlights of this release:
 - Compiling and testing with Java 22
 - Cacheable Groovy script compilation
 - New methods in lazy collection properties

For more details see https://docs.gradle.org/8.7/release-notes.html

Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details

Enter target Java version (min: 7, default: 21): 17

Select application structure:
  1: Single application project
  2: Application and library project
Enter selection (default: Single application project) [1..2] 1

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no


> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.7/samples/sample_building_java_applications_multi_project.html

BUILD SUCCESSFUL in 5m 12s
1 actionable task: 1 executed

./settings.gradle

plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}

rootProject.name = 'example'
include('app')

./app/build.gradle

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava
}

// Apply a specific Java toolchain to ease working on different environments.
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    // Define the main class for the application.
    mainClass = 'com.dedetok.App'
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

./gradle/libs.versions.toml

[versions]
guava = "32.1.3-jre"
junit-jupiter = "5.10.1"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

To show current gradle version

$ ./gradlew --version

------------------------------------------------------------
Gradle 8.7
------------------------------------------------------------

Build time:   2024-03-22 15:52:46 UTC
Revision:     650af14d7653aa949fce5e886e685efc9cf97c10

Kotlin:       1.9.22
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          17.0.16 (Debian 17.0.16+8-Debian-1deb12u1)
OS:           Linux 6.12.57+deb13-amd64 amd64

Test run

$ ./gradlew run
Path for java installation '/usr/lib/jvm/openjdk-17' (Common Linux Locations) does not contain a java executable

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 473ms
2 actionable tasks: 1 executed, 1 up-to-date

or you can run using style $ ./gradlew :app:run

Now add maridb jcoonect into project, using Traditional Groovy Approach. edit ./app/build.gradle

...
dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava

    // mariadb jconnector (single file no need change libs.versions.toml
    implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.3'

    // using libs.versions.toml must editing this file 
    //implementation libs.mariadb.client

}
...

If you wish to useing Version Catalog Approach, use 2nd option for ./app/build.gradle and edit libs.version.toml

[versions]
guava = "32.1.3-jre"
junit-jupiter = "5.10.1"
mariadb = "3.3.3"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
mariadb-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version.ref = "mariadb" }

use Version Catalog Approach for complex project and involving multiple developers.

Edit ./app/src/main/java/com/dedetok/App.java

/*
 * This source file was generated by the Gradle 'init' task
 */
package com.dedetok;

import java.sql.*;

public class App {
    public String getGreeting() {
        return "Hello World!";
    }

    public static void main(String[] args) throws SQLException {
        System.out.println(new App().getGreeting());
        
        String dbUrl = "jdbc:mariadb://localhost:3306/mydatabase";
        String dbUName = "myuname";
        String dbPass = "mypass";
        
        Connection conn = DriverManager.getConnection(dbUrl, dbUName, dbPass);

    }
}

To generate single file jar for your project, edit  ./app/build.gradle

...
jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    // change with yours
    manifest {
        attributes "Main-Class": "com.dedetok.App"
    }

    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
...

You can run directly using java command

$ ./gradlew build
...
$ java -jar ./app/build/libs/app.jar
Hello World!

Your jar file location is ./app/build/libs/

Now you can edit  ./app/src/main/java/com/dedetok/App.java finish your project. Use any text editor if you wish.