Thursday, January 29, 2026

Android java: write and delete file into folder creeated by application in 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