Creating a Custom Number Picker Dialog in Android Using Volley and DialogFragment
DialogFragment, Volley HTTP networking, and custom interfaces to fetch and select PL numbers seamlessly.In inventory, manufacturing, and enterprise applications—such as those used in rail coach production—Parts List (PL) numbers are essential for identifying items and components. Allowing users to search and select these PL numbers via an interactive dialog prevents manual typing errors and provides a cleaner user experience.This article covers how to create a searchable modal dialog in Android using Java, Volley for asynchronous HTTP requests, and clean UI patterns to make an EditText act as a click-to-select trigger.
Key Requirements
DialogFragment: To display the search interface as a floating modal window.
Volley Library: For lightweight, asynchronous POST requests with automatic request cancellation.
Debounce Handler: To introduce a short delay (300ms) while typing, preventing unnecessary network calls on every keystroke.
Custom Listener Interface: To pass selected PL data back to the calling
ActivityorFragment.
Step 1: Designing the Layout
The search dialog layout (dialog_pl_search.xml) requires an AutoCompleteTextView for search input, a ListView for search results, a ProgressBar for loading feedback, and a fallback TextView when no records match.
Step 2: Implementing PlSearchDialog Class
The PlSearchDialog extends DialogFragment. It parses JSON responses from the target API (PLNumberQuery.php) and uses a custom callback interface to report selection events.
public class PlSearchDialog extends DialogFragment {
private static final String DATA_LOC = "https://jyotishgher.in/cmms/PL/PLNumberQuery.php";
private AutoCompleteTextView searchInput;
private ListView resultsList;
private ProgressBar progressBar;
private TextView noResultsText;
private Button cancelButton;
private ArrayAdapter<String> resultsAdapter;
private ArrayList<String> displayList = new ArrayList<>();
private ArrayList<JSONObject> plDataList = new ArrayList<>();
private RequestQueue requestQueue;
private Handler handler = new Handler();
private Runnable searchRunnable;
private OnPlSelectedListener listener;
public interface OnPlSelectedListener {
void onPlSelected(String plNo, String shortDesc, String fullDesc, String prodYr);
}
public void setOnPlSelectedListener(OnPlSelectedListener listener) {
this.listener = listener;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
LayoutInflater inflater = requireActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_pl_search, null);
initializeViews(view);
setupSearch();
builder.setView(view).setTitle("Select PL Number");
return builder.create();
}
private void initializeViews(View view) {
searchInput = view.findViewById(R.id.search_input);
resultsList = view.findViewById(R.id.results_list);
progressBar = view.findViewById(R.id.progress_bar);
noResultsText = view.findViewById(R.id.no_results_text);
cancelButton = view.findViewById(R.id.cancel_button);
requestQueue = Volley.newRequestQueue(requireContext());
cancelButton.setOnClickListener(v -> dismiss());
resultsAdapter = new ArrayAdapter<>(requireContext(),
android.R.layout.simple_list_item_1, displayList);
resultsList.setAdapter(resultsAdapter);
resultsList.setOnItemClickListener((parent, view1, position, id) -> {
try {
JSONObject selected = plDataList.get(position);
String plNo = selected.optString("PL_NO", "");
String shortDesc = selected.optString("SHORT_DESC", "");
String fullDesc = selected.optString("FULL_DESC", "");
String prodYr = selected.optString("PROD_YR", "");
if (listener != null) {
listener.onPlSelected(plNo, shortDesc, fullDesc, prodYr);
}
dismiss();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getContext(), "Error selecting item", Toast.LENGTH_SHORT).show();
}
});
}
private void setupSearch() {
searchInput.setThreshold(2);
searchInput.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (searchRunnable != null) {
handler.removeCallbacks(searchRunnable);
}
String query = s.toString().trim();
if (query.length() >= 2) {
progressBar.setVisibility(View.VISIBLE);
noResultsText.setVisibility(View.GONE);
displayList.clear();
plDataList.clear();
resultsAdapter.notifyDataSetChanged();
searchRunnable = () -> searchPlNumbers(query);
handler.postDelayed(searchRunnable, 300);
} else {
displayList.clear();
plDataList.clear();
resultsAdapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE);
noResultsText.setVisibility(View.GONE);
}
}
@Override
public void afterTextChanged(Editable s) {}
});
}
private void searchPlNumbers(String query) {
if (requestQueue != null) {
requestQueue.cancelAll("pl_search");
}
StringRequest request = new StringRequest(Request.Method.POST, DATA_LOC,
response -> {
progressBar.setVisibility(View.GONE);
try {
JSONObject json = new JSONObject(response);
JSONArray results = json.getJSONArray("result");
displayList.clear();
plDataList.clear();
if (results.length() == 0) {
noResultsText.setVisibility(View.VISIBLE);
noResultsText.setText("No PL items found.");
} else {
noResultsText.setVisibility(View.GONE);
for (int i = 0; i < results.length(); i++) {
JSONObject item = results.getJSONObject(i);
plDataList.add(item);
String plNo = item.optString("PL_NO", "");
String shortDesc = item.optString("SHORT_DESC", "");
displayList.add(plNo + " - " + shortDesc);
}
}
resultsAdapter.notifyDataSetChanged();
} catch (Exception e) {
e.printStackTrace();
noResultsText.setVisibility(View.VISIBLE);
noResultsText.setText("Error loading PL items.");
}
},
error -> {
progressBar.setVisibility(View.GONE);
noResultsText.setVisibility(View.VISIBLE);
noResultsText.setText("Network error. Check connection.");
}) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("pl_no", query);
return params;
}
@Override
public String getTag() {
return "pl_search";
}
};
requestQueue.add(request);
}
@Override
public void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
if (requestQueue != null) {
requestQueue.cancelAll("pl_search");
}
}
}
Step 3: Triggering Dialog from Non-Editable EditText
To configure an EditText field to open the dialog upon click—while preventing direct keyboard input—disable focusability in both layout XML and Java code.
<EditText
android:id="@+id/etPLNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Select PL Number"
android:focusable="false"
android:clickable="true"
android:cursorVisible="false" />
Activity / Fragment Setup
Using Handler.postDelayed() with a 300ms threshold prevents server spamming while users type long search queries. Calling requestQueue.cancelAll("pl_search") in onDestroy() and prior to new searches prevents race conditions and memory leaks.Combining focusable="false" with setOnClickListener repurposes a standard text field into an interactive picker without triggering soft keyboard displays.


Post a Comment
0Comments