Wednesday, May 15, 2024

Android Java: open activity for result to pick up / choose a file in Shared Folder - Download /storage/emulated/0/Download

Note:

Moving from startActivityForResult to ActivityResultLauncher to pickup file in Shared Folder - Download /storage/emulated/0/Download

Minimum Android 7 API 24 Targeted SDK Android 24 API 34

build.gradle.kts (Module :app)

plugins {
    alias(libs.plugins.android.application)
}

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

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

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

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

dependencies {

    implementation(libs.appcompat)
    implementation(libs.material)
    implementation(libs.activity)
    implementation(libs.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)
}

AndroidManifest.xml

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

    <application
        android:requestLegacyExternalStorage="true"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TutorialSharedFolder"
        tools:targetApi="31">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

package com.dedetok.tutorialsharedfolder;

import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;

import android.provider.DocumentsContract;

public class MainActivity extends AppCompatActivity {

    /*
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
    <application
        android:requestLegacyExternalStorage="true"

    /storage/emulated/0/Download

    MediaStore.Downloads (/storage/emulated/0/Download) Android 10 (API level 29) and higher,
     */


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

        /*
         * check perrmission
         */
        boolean isPermitted;
        String myPermissionRequired = android.Manifest.permission.READ_EXTERNAL_STORAGE;
        isPermitted = myCheckPermission(this, myPermissionRequired);
        Log.e("dedetok", "READ_EXTERNAL_STORAGE: "+isPermitted);

        myPermissionRequired = android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
        isPermitted = myCheckPermission(this, myPermissionRequired);
        Log.e("dedetok", "WRITE_EXTERNAL_STORAGE: "+isPermitted);
        myPermissionRequired = Manifest.permission.MANAGE_EXTERNAL_STORAGE;
        isPermitted = myCheckPermission(this, myPermissionRequired);
        Log.e("dedetok", "MANAGE_EXTERNAL_STORAGE: "+isPermitted);

        /*
         * root Environment.getExternalStorageDirectory() -> /storage/emulated/0
         */
        // get root directory path NOT USED
        File fExternalStorage = Environment.getExternalStorageDirectory();
        String sFExternalStorage = fExternalStorage.getPath();
        //String fsFExternalStorage = sFExternalStorage+File.separator+sFilename;
        Log.e("dedetok", "Environment.getExternalStorageDirectory() "+sFExternalStorage); //debug

        /*
         * Access file in /storage/emulated/0/Download
         * Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) ->
         *          /storage/emulated/0/Download
         */
        // get Download directory path NOT USED IN PICK FILE BUT USED IN PRINT FILE
        File extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        String dirPath = extDir.getPath();
        Log.e("dedetok", "getExternalStoragePublicDirectory "+dirPath); //debug

        /*
         * Select file in Download Folder
         */
        //getFileURI(); // OLD WAT Deprecated
        getFileURINew(); // New Way using system dialog to select file

        /*
         * writing to Download Folder
         */
        String sFileOutput = extDir+File.separator+"helloworld.txt";
        File fOutput = new File(sFileOutput);
        try {
            PrintWriter myPW = new PrintWriter(fOutput);
            myPW.println("Hello World");
            myPW.println("this is a test");
            myPW.flush();
            myPW.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace(); // debug
        }


    }

    /*
     * self check permission function
     */
    private boolean myCheckPermission(Context appContext, String sPermission) {
        return (ContextCompat.checkSelfPermission(appContext, sPermission)
                == PackageManager.PERMISSION_GRANTED);
        // or
        // return (PermissionChecker.checkSelfPermission(appContext, sPermission)
        //        == PermissionChecker.PERMISSION_GRANTED);
    }

    ///////////// THIS IS PART OF DEPRECATED SECTION START /////////////
    /*
     * get result
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode,
                                    Intent resultData) {
        super.onActivityResult(requestCode, resultCode, resultData);
        if (requestCode == MY_ID_PICK_FILE
                && resultCode == Activity.RESULT_OK) {
            // The result data contains a URI for the document or directory that
            // the user selected.
            Uri uri;
            if (resultData != null) {
                uri = resultData.getData();
                // Perform operations on the document using its URI.
                Log.e("dedetok", "Result"); // debug
                if (uri!=null) {

                    Log.e("dedetok", "Result uri: "+uri.getPath()); // debug
                    openMyFile(uri);
                }
            }
        }

    }
    private static final int MY_ID_PICK_FILE = 1; // any number
    private static final String mimeTypeFilter = "text/csv";
    /*
     * https://developer.android.com/training/data-storage/shared/documents-files
     * https://stackoverflow.com/questions/71667342/how-to-allow-selection-of-csv-in-a-file-chooser-intent-in-android
     * Deprecated startActivityForResult
     */
    private void getFileURI() {
        String[] myMime = {"*/*",
                "text/plain",
                "application/csv",
                "application/vnd.ms-excel",
                "application/excel",
                "application/x-excel",
                "application/x-msexcel"
        };
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // MUST
        intent.addCategory(Intent.CATEGORY_OPENABLE); // MUST
        intent.setType(mimeTypeFilter);

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        intent.putExtra(Intent.EXTRA_MIME_TYPES, myMime);

        startActivityForResult(intent, MY_ID_PICK_FILE);
    }
    ///////////// THIS IS PART OF DEPRECATED SECTION END /////////////

    ///////////// THIS PART OF NEW WAY START /////////////
    /*
     * https://developer.android.com/training/basics/intents/result
     */
    private void getFileURINew() {
        String[] myMime = {"*/*",
                "text/plain",
                "application/csv",
                "application/vnd.ms-excel",
                "application/excel",
                "application/x-excel",
                "application/x-msexcel"
        };
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // MUST
        intent.addCategory(Intent.CATEGORY_OPENABLE); // MUST
        intent.setType(mimeTypeFilter);

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        intent.putExtra(Intent.EXTRA_MIME_TYPES, myMime);

        mGetContent.launch(intent);
    }
    // ActivityResultCallback
    ActivityResultCallback<ActivityResult> myActivityResult = new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult aResult) {
            if (aResult.getResultCode()==RESULT_OK) {
                Intent iData = aResult.getData();
                Uri uriData = iData.getData();
                Log.e("dedetok", "new "+uriData.getPath());

            }

        }
    };
    // ActivityResultContracts
    ActivityResultContracts.StartActivityForResult myARC = new ActivityResultContracts.StartActivityForResult();
    // ActivityResultLauncher
    ActivityResultLauncher<Intent> mGetContent = registerForActivityResult(
        myARC,
        myActivityResult);
    ///////////// THIS PART OF NEW WAY END /////////////

    ///////////// SUPPORTING FUNCTION /////////////
    /*
     * Open File after receive from system pick up
     */
    private void openMyFile(Uri uriMyFile) {
        //getFileURI(); // crash
        StringBuilder stringBuilder = new StringBuilder();
        if (isVirtualFile(uriMyFile)) {
            Log.e("dedetok","It is Virual"); // debug
            ContentResolver resolver = getContentResolver();

            String[] openableMimeTypes = resolver.getStreamTypes(uriMyFile, mimeTypeFilter);

            if (openableMimeTypes != null ||
                    openableMimeTypes.length > 0) {

                try {
                    InputStream inputStream = resolver
                            .openTypedAssetFileDescriptor(uriMyFile, openableMimeTypes[0], null)
                            .createInputStream();
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(inputStream));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        stringBuilder.append(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace(); // debug
                }
            }

        } else {
            try {
                InputStream inputStream =
                         getContentResolver().openInputStream(uriMyFile);
                BufferedReader reader = new BufferedReader(
                         new InputStreamReader(inputStream));
                String line;
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace(); // debug
            }
        }
        Log.e("dedetok",stringBuilder.toString());
    }

    /*
     * check if file receive is virtual
     * Android 7.0 (API level 24) and higher
     *https://developer.android.com/training/data-storage/shared/documents-files
     */
    private boolean isVirtualFile(Uri uri) {
        if (!DocumentsContract.isDocumentUri(this, uri)) {
            return false;
        }

        Cursor cursor = getContentResolver().query(
                uri,
                new String[] { DocumentsContract.Document.COLUMN_FLAGS },
                null, null, null);

        int flags = 0;
        if (cursor.moveToFirst()) {
            flags = cursor.getInt(0);
        }
        cursor.close();

        return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
    }

}

References:

  • https://developer.android.com/training/basics/intents/result
  • https://devofandroid.blogspot.com/2022/09/get-result-from-another-activity-using.html
  • https://medium.com/@steves2001/moving-from-android-startactivityforresult-to-registerforactivityresult-76ca04044ff1
  • https://www.geeksforgeeks.org/how-to-use-activityforresultluncher-as-startactivityforresult-is-deprecated-in-android/


Thursday, May 2, 2024

Ukuran ban dan merek di Indonesia

Ban 185/60 R14

  1. Bridgestone Turanza 185/60 R14
  2. GT Radial Eco 185/60 R14
  3. GT Radial Ecotec 185/60 R14
  4. Dunlop SP Touring R1 185/60 R14
  5. Goodyear Assurance Triplemax 185/60 R14
  6. Goodyear Assurance Triplemax2 185/60 R14
  7. Hankook Kinergy Eco K435 185/60 R14 
  8. Achilles 868 All Seasons - 185/60 R14 82H
  9. Achilles 122 185/60 R14
  10. Accelera Epsilon 185/60 R14 1 arah
  11. BFGoodrich Advantage Touring 185 60 R14 
  12. Falken Sincera SN832i 185/60 R14 
  13. Forceum D 600 185/60 R14
  14. Michelin Energy XM2+ 185/60 R14
  15. Michelin Energy XM2 185/60 R14 
  16. Toyo Tires NanoEnergy 3 (TTM) 185/60 R14
Ban 175/65 R14 *
  1. Bridgestone Ecopia EP 150 175/65 R14
  2. Bridgestone Turanza 175/65 R14
  3. Bridgestone Techno 175/65 R14 
  4. GT Radial Champiro Ecotec 175/65 R14 
  5. GT Radial Champiro Eco 175/65 R14 
  6. Dunlop Enasave EC300+ 175/65 R14
  7. Dunlop SP Touring R1 175/65 R14
  8. Goodyear Assurance Duraplus 2 175/65 R14
  9. Goodyear Assurance Duraplus 175/65 R14 
  10. Goodyear Assurance TripleMax 175/65 R14
  11. Hankook Kinergy Eco2 175/65 R14 
  12. Hankook Kinergy Eco 175/65 R14 
  13. Hankook Optiomo H724 175/65 R14
  14. Achilles All Seasons 868 175/65 R14
  15. Achilles 122 - 175/65 R14 82H
  16. Achilles Platinum 175/65 R14
  17. Accelera Eco Plush 175/65 R14
  18. BFGoodrich Advantage Touring 175/65 R14
  19. Falken Sincera SN832i 175/65 R14
  20. Forceum Ecosa 175/65 R14
  21. Forceum N 300 175/65 R14
  22. Michelin Energy XM2+ 175/65 R14
  23. Michelin Energy XM2 175/65 R14
  24. Toyo Tires NanoEnergy 3 (TTM) 175/65 R14 

Ban 216/65 R16 tanggal pencarian: 22-10-2024

  1. Bridgestone Turanza T005A 216/65 R16
  2. Bridgestone Dueler 689 215/65 R16
  3. GT Radial Savero SUV 215/65 R16
  4. GT Radial Champiro Ecotec 215/65 R16
  5. Dunlop Grandtrek ST20 216/65 R16
  6. Dunlop Enasave EC300+ 215/65 R16
  7. Goodyear Maxguard Suv 215/65 R16
  8. Hankook Dynapro HT 215/65 R16
  9. Achilles 122 215/65 R16
  10. Achilles Desert Hawk H/T 2 Ukuran 215/65 R16
  11. Accelera Eco Plush 215/65 R16
  12. BFGoodrich Advantage Touring 215/65 R16
  13. Falken ZE914 215/65 R16
  14. Forceum Hena 215/65 R16
  15. Michelin Energy XM2 215/65 R16
  16. Michelin Primacy SUV 215/65 R16
  17. Toyo Tranpath R30 215/65 R16
  18. Toyo Tires Proxes ST III 215/65 R16

 Catatan: beberapa tipe mungkin sudah tidak diproduksi dan atau impor.

 



Friday, April 26, 2024

Android java: using SQLiteOpenHelper instead Room Library

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

Here is code for extending SQLiteOpenHelper:

package com.dedetok.radiowalkman.mydb;

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

import androidx.annotation.Nullable;

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

public class MyDBHelper extends SQLiteOpenHelper {

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

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

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

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

    }

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

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

    }

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

}

 This is java class in Radio Walkman application.

Thursday, April 18, 2024

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

Source code: 

com.dedetok.tutorialcustomjlist.Person.java

package com.dedetok.tutorialcustomjlist;

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

com.dedetok.tutorialcustomjlist.MyListModel.java

package com.dedetok.tutorialcustomjlist;

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

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

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

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

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

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

}

com.dedetok.tutorialcustomjlist.MyCBRenderer.java

package com.dedetok.tutorialcustomjlist;

import java.awt.Component;

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

public class MyCBRenderer extends BasicComboBoxRenderer {

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

com.dedetok.tutorialcustomjlist.TutorialJList.java

package com.dedetok.tutorialcustomjlist;

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

public class TutorialJList {

    private JFrame frame;

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

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

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

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


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

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

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

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