CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-google-android-material--material

Google's official Material Design components library for Android applications with comprehensive UI components, theming, and animations.

Pending
Overview
Eval results
Files

pickers-and-selection.mddocs/

Pickers and Selection

Date and time pickers for user input with Material Design styling and behavior.

Core Imports

import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.DateValidatorPointBackward;
import com.google.android.material.datepicker.CompositeDateValidator;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
import android.util.Pair;

Material Date Picker

Material Design date picker dialog for single dates or date ranges.

class MaterialDatePicker<S> extends DialogFragment {
    // Factory methods for single date selection
    static Builder<Long> todayInUtcMilliseconds();
    static Builder<Long> datePicker();
    
    // Factory method for date range selection
    static Builder<Pair<Long, Long>> dateRangePicker();
    
    // Selection access
    S getSelection();
    String getHeaderText();
    
    // Positive button listeners
    boolean addOnPositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener<S> listener);
    boolean removeOnPositiveButtonClickListener(MaterialPickerOnPositiveButtonClickListener<S> listener);
    void clearOnPositiveButtonClickListeners();
    
    // Negative button listeners  
    boolean addOnNegativeButtonClickListener(View.OnClickListener listener);
    boolean removeOnNegativeButtonClickListener(View.OnClickListener listener);
    void clearOnNegativeButtonClickListeners();
    
    // Cancel listeners
    boolean addOnCancelListener(DialogInterface.OnCancelListener listener);
    boolean removeOnCancelListener(DialogInterface.OnCancelListener listener);
    void clearOnCancelListeners();
    
    // Dismiss listeners
    boolean addOnDismissListener(DialogInterface.OnDismissListener listener);
    boolean removeOnDismissListener(DialogInterface.OnDismissListener listener);
    void clearOnDismissListeners();
}

interface MaterialPickerOnPositiveButtonClickListener<S> {
    void onPositiveButtonClick(S selection);
}

class MaterialDatePicker.Builder<S> {
    Builder<S> setSelection(S selection);
    Builder<S> setCalendarConstraints(CalendarConstraints calendarConstraints);
    Builder<S> setTheme(@StyleRes int themeResId);
    Builder<S> setTitleText(@StringRes int titleTextResId);
    Builder<S> setTitleText(CharSequence charSequence);
    Builder<S> setPositiveButtonText(@StringRes int positiveButtonTextResId);
    Builder<S> setPositiveButtonText(CharSequence charSequence);
    Builder<S> setNegativeButtonText(@StringRes int negativeButtonTextResId);
    Builder<S> setNegativeButtonText(CharSequence charSequence);
    Builder<S> setInputMode(int inputMode);
    MaterialDatePicker<S> build();
}

Date Picker Input Mode Constants

public static final int INPUT_MODE_CALENDAR = 0;
public static final int INPUT_MODE_TEXT = 1;

Usage Example

// Single date picker
MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
    .setTitleText("Select date")
    .setSelection(MaterialDatePicker.todayInUtcMilliseconds())
    .build();

datePicker.addOnPositiveButtonClickListener(selection -> {
    // Handle selected date (in UTC milliseconds)
    SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
    String selectedDate = sdf.format(new Date(selection));
    dateButton.setText(selectedDate);
});

datePicker.show(getSupportFragmentManager(), "date_picker");

// Date range picker
MaterialDatePicker<Pair<Long, Long>> dateRangePicker = MaterialDatePicker.Builder.dateRangePicker()
    .setTitleText("Select dates")
    .build();

dateRangePicker.addOnPositiveButtonClickListener(selection -> {
    Long startDate = selection.first;
    Long endDate = selection.second;
    
    SimpleDateFormat sdf = new SimpleDateFormat("MMM dd", Locale.getDefault());
    String dateRange = sdf.format(new Date(startDate)) + " - " + sdf.format(new Date(endDate));
    dateRangeButton.setText(dateRange);
});

dateRangePicker.show(getSupportFragmentManager(), "date_range_picker");

// Date picker with constraints
CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder();
constraintsBuilder.setStart(MaterialDatePicker.todayInUtcMilliseconds());
constraintsBuilder.setEnd(MaterialDatePicker.todayInUtcMilliseconds() + TimeUnit.DAYS.toMillis(365));
constraintsBuilder.setValidator(DateValidatorPointForward.now());

MaterialDatePicker<Long> constrainedPicker = MaterialDatePicker.Builder.datePicker()
    .setCalendarConstraints(constraintsBuilder.build())
    .setTitleText("Select future date")
    .build();

Calendar Constraints

Configuration for calendar navigation bounds and date validation.

class CalendarConstraints {
    Month getStart();
    Month getEnd();
    Month getOpenAt();
    DateValidator getDateValidator();
    int getYearSpan();
    int getMonthSpan();
}

class CalendarConstraints.Builder {
    Builder setStart(long month);
    Builder setEnd(long month);
    Builder setOpenAt(long month);
    Builder setValidator(DateValidator validator);
    CalendarConstraints build();
}

interface CalendarConstraints.DateValidator {
    boolean isValid(long date);
}

Date Validators

// Forward from a specific date
class DateValidatorPointForward implements CalendarConstraints.DateValidator {
    static DateValidatorPointForward from(long point);
    static DateValidatorPointForward now();
    boolean isValid(long date);
}

// Backward to a specific date  
class DateValidatorPointBackward implements CalendarConstraints.DateValidator {
    static DateValidatorPointBackward before(long point);
    static DateValidatorPointBackward now();
    boolean isValid(long date);
}

// Composite validator combining multiple validators
class CompositeDateValidator implements CalendarConstraints.DateValidator {
    static DateValidator allOf(List<DateValidator> validators);
    static DateValidator anyOf(List<DateValidator> validators);
    boolean isValid(long date);
}

Usage Example

// Date picker with validation - only future dates
CalendarConstraints constraints = new CalendarConstraints.Builder()
    .setStart(MaterialDatePicker.todayInUtcMilliseconds())
    .setValidator(DateValidatorPointForward.now())
    .build();

MaterialDatePicker<Long> futureDatePicker = MaterialDatePicker.Builder.datePicker()
    .setCalendarConstraints(constraints)
    .setTitleText("Select appointment date")
    .build();

// Date picker with range constraints - only current year
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MONTH, Calendar.JANUARY);
calendar.set(Calendar.DAY_OF_MONTH, 1);
long yearStart = calendar.getTimeInMillis();

calendar.set(Calendar.MONTH, Calendar.DECEMBER);
calendar.set(Calendar.DAY_OF_MONTH, 31);
long yearEnd = calendar.getTimeInMillis();

CalendarConstraints yearConstraints = new CalendarConstraints.Builder()
    .setStart(yearStart)
    .setEnd(yearEnd)
    .setOpenAt(MaterialDatePicker.todayInUtcMilliseconds())
    .build();

// Composite validator - weekdays only and future dates
DateValidator weekdaysOnly = new DateValidator() {
    @Override
    public boolean isValid(long date) {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(date);
        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
    }
};

DateValidator compositeValidator = CompositeDateValidator.allOf(Arrays.asList(
    DateValidatorPointForward.now(),
    weekdaysOnly
));

CalendarConstraints businessDayConstraints = new CalendarConstraints.Builder()
    .setValidator(compositeValidator)
    .build();

Material Time Picker

Material Design time picker dialog for selecting time values.

class MaterialTimePicker extends DialogFragment {
    // Factory method
    static Builder builder();
    
    // Time access
    int getHour();
    int getMinute();
    int getInputMode();
    
    // Positive button listeners
    boolean addOnPositiveButtonClickListener(View.OnClickListener listener);
    boolean removeOnPositiveButtonClickListener(View.OnClickListener listener);
    void clearOnPositiveButtonClickListeners();
    
    // Negative button listeners
    boolean addOnNegativeButtonClickListener(View.OnClickListener listener);
    boolean removeOnNegativeButtonClickListener(View.OnClickListener listener);
    void clearOnNegativeButtonClickListeners();
    
    // Cancel listeners
    boolean addOnCancelListener(DialogInterface.OnCancelListener listener);
    boolean removeOnCancelListener(DialogInterface.OnCancelListener listener);
    void clearOnCancelListeners();
    
    // Dismiss listeners
    boolean addOnDismissListener(DialogInterface.OnDismissListener listener);
    boolean removeOnDismissListener(DialogInterface.OnDismissListener listener);
    void clearOnDismissListeners();
}

class MaterialTimePicker.Builder {
    Builder setTimeFormat(@TimeFormat int format);
    Builder setHour(@IntRange(from = 0, to = 23) int hour);
    Builder setMinute(@IntRange(from = 0, to = 59) int minute);
    Builder setTitleText(@StringRes int titleTextResId);
    Builder setTitleText(CharSequence charSequence);
    Builder setPositiveButtonText(@StringRes int positiveButtonTextResId);
    Builder setPositiveButtonText(CharSequence charSequence);
    Builder setNegativeButtonText(@StringRes int negativeButtonTextResId);  
    Builder setNegativeButtonText(CharSequence charSequence);
    Builder setInputMode(int inputMode);
    Builder setTheme(@StyleRes int themeResId);
    MaterialTimePicker build();
}

Time Picker Input Mode Constants

public static final int INPUT_MODE_CLOCK = 0;
public static final int INPUT_MODE_KEYBOARD = 1;

Time Format Constants

class TimeFormat {
    public static final int CLOCK_12H = 0;
    public static final int CLOCK_24H = 1;
}

Usage Example

// Basic time picker
MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
    .setTimeFormat(TimeFormat.CLOCK_12H)
    .setHour(12)
    .setMinute(0)
    .setTitleText("Select appointment time")
    .build();

timePicker.addOnPositiveButtonClickListener(v -> {
    int hour = timePicker.getHour();
    int minute = timePicker.getMinute();
    
    // Format time for display
    String timeString = String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
    timeButton.setText(timeString);
    
    // Convert to 12-hour format if needed
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, hour);
    calendar.set(Calendar.MINUTE, minute);
    
    SimpleDateFormat sdf = new SimpleDateFormat("h:mm a", Locale.getDefault());
    String formattedTime = sdf.format(calendar.getTime());
    timeButton.setText(formattedTime);
});

timePicker.show(getSupportFragmentManager(), "time_picker");

// 24-hour format time picker
MaterialTimePicker timePicker24h = new MaterialTimePicker.Builder()
    .setTimeFormat(TimeFormat.CLOCK_24H)
    .setHour(14)
    .setMinute(30)
    .setTitleText("Meeting time")
    .setInputMode(MaterialTimePicker.INPUT_MODE_KEYBOARD)
    .build();

timePicker24h.addOnPositiveButtonClickListener(v -> {
    int hour = timePicker24h.getHour();
    int minute = timePicker24h.getMinute();
    
    String time24h = String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
    timeButton.setText(time24h);
});

// Time picker with validation
MaterialTimePicker businessHoursPicker = new MaterialTimePicker.Builder()
    .setTimeFormat(TimeFormat.CLOCK_12H)
    .setHour(9)
    .setMinute(0)
    .setTitleText("Business hours only")
    .build();

businessHoursPicker.addOnPositiveButtonClickListener(v -> {
    int hour = businessHoursPicker.getHour();
    int minute = businessHoursPicker.getMinute();
    
    // Validate business hours (9 AM - 5 PM)
    if (hour < 9 || hour >= 17) {
        showErrorDialog("Please select a time between 9:00 AM and 5:00 PM");
        return;
    }
    
    // Save valid time
    saveAppointmentTime(hour, minute);
});

Complete Date/Time Selection Example

Example showing combined date and time selection with validation:

public class AppointmentSchedulerActivity extends AppCompatActivity {
    private MaterialButton dateButton;
    private MaterialButton timeButton;
    private MaterialButton scheduleButton;
    
    private Long selectedDateMillis;
    private Integer selectedHour;
    private Integer selectedMinute;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_appointment_scheduler);
        
        initViews();
        setupDatePicker();
        setupTimePicker();
        updateScheduleButton();
    }
    
    private void initViews() {
        dateButton = findViewById(R.id.date_button);
        timeButton = findViewById(R.id.time_button);
        scheduleButton = findViewById(R.id.schedule_button);
        
        scheduleButton.setOnClickListener(v -> scheduleAppointment());
    }
    
    private void setupDatePicker() {
        dateButton.setOnClickListener(v -> {
            // Create constraints for future dates only
            CalendarConstraints constraints = new CalendarConstraints.Builder()
                .setStart(MaterialDatePicker.todayInUtcMilliseconds())
                .setValidator(DateValidatorPointForward.now())
                .build();
            
            MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
                .setTitleText("Select appointment date")
                .setSelection(selectedDateMillis != null ? selectedDateMillis : MaterialDatePicker.todayInUtcMilliseconds())
                .setCalendarConstraints(constraints)
                .build();
            
            datePicker.addOnPositiveButtonClickListener(selection -> {
                selectedDateMillis = selection;
                
                // Format date for display
                SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
                String dateString = sdf.format(new Date(selection));
                dateButton.setText(dateString);
                
                updateScheduleButton();
            });
            
            datePicker.show(getSupportFragmentManager(), "date_picker");
        });
    }
    
    private void setupTimePicker() {
        timeButton.setOnClickListener(v -> {
            MaterialTimePicker timePicker = new MaterialTimePicker.Builder()
                .setTimeFormat(TimeFormat.CLOCK_12H)
                .setHour(selectedHour != null ? selectedHour : 9)
                .setMinute(selectedMinute != null ? selectedMinute : 0)
                .setTitleText("Select appointment time")
                .build();
            
            timePicker.addOnPositiveButtonClickListener(view -> {
                int hour = timePicker.getHour();
                int minute = timePicker.getMinute();
                
                // Validate business hours
                if (!isValidBusinessHour(hour)) {
                    showBusinessHoursError();
                    return;
                }
                
                selectedHour = hour;
                selectedMinute = minute;
                
                // Format time for display
                Calendar calendar = Calendar.getInstance();
                calendar.set(Calendar.HOUR_OF_DAY, hour);
                calendar.set(Calendar.MINUTE, minute);
                
                SimpleDateFormat timeFormat = new SimpleDateFormat("h:mm a", Locale.getDefault());
                String timeString = timeFormat.format(calendar.getTime());
                timeButton.setText(timeString);
                
                updateScheduleButton();
            });
            
            timePicker.show(getSupportFragmentManager(), "time_picker");
        });
    }
    
    private boolean isValidBusinessHour(int hour) {
        return hour >= 9 && hour < 17; // 9 AM to 5 PM
    }
    
    private void showBusinessHoursError() {
        new MaterialAlertDialogBuilder(this)
            .setTitle("Invalid Time")
            .setMessage("Please select a time between 9:00 AM and 5:00 PM")
            .setPositiveButton("OK", null)
            .show();
    }
    
    private void updateScheduleButton() {
        boolean canSchedule = selectedDateMillis != null && selectedHour != null && selectedMinute != null;
        scheduleButton.setEnabled(canSchedule);
    }
    
    private void scheduleAppointment() {
        if (selectedDateMillis == null || selectedHour == null || selectedMinute == null) {
            return;
        }
        
        // Create appointment datetime
        Calendar appointmentTime = Calendar.getInstance();
        appointmentTime.setTimeInMillis(selectedDateMillis);
        appointmentTime.set(Calendar.HOUR_OF_DAY, selectedHour);
        appointmentTime.set(Calendar.MINUTE, selectedMinute);
        appointmentTime.set(Calendar.SECOND, 0);
        appointmentTime.set(Calendar.MILLISECOND, 0);
        
        // Check if appointment is at least 1 hour in the future
        Calendar now = Calendar.getInstance();
        now.add(Calendar.HOUR_OF_DAY, 1);
        
        if (appointmentTime.before(now)) {
            new MaterialAlertDialogBuilder(this)
                .setTitle("Invalid Appointment Time")
                .setMessage("Appointments must be scheduled at least 1 hour in advance.")
                .setPositiveButton("OK", null)
                .show();
            return;
        }
        
        // Show confirmation dialog
        SimpleDateFormat fullFormat = new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' h:mm a", Locale.getDefault());
        String appointmentString = fullFormat.format(appointmentTime.getTime());
        
        new MaterialAlertDialogBuilder(this)
            .setTitle("Confirm Appointment")
            .setMessage("Schedule appointment for " + appointmentString + "?")
            .setPositiveButton("Confirm", (dialog, which) -> {
                // Save appointment
                saveAppointment(appointmentTime);
                showConfirmationSnackbar();
            })
            .setNegativeButton("Cancel", null)
            .show();
    }
    
    private void saveAppointment(Calendar appointmentTime) {
        // Save to database or send to server
        // Implementation depends on your data storage solution
    }
    
    private void showConfirmationSnackbar() {
        Snackbar.make(findViewById(android.R.id.content), 
            "Appointment scheduled successfully", Snackbar.LENGTH_LONG)
            .setAction("View Details", v -> {
                // Navigate to appointment details
            })
            .show();
    }
}

// Date range picker for vacation booking
public class VacationBookingFragment extends Fragment {
    private MaterialButton dateRangeButton;
    private TextView selectedDatesText;
    
    private void setupDateRangePicker() {
        dateRangeButton.setOnClickListener(v -> {
            // Create constraints - no weekends, future dates only
            DateValidator weekdaysOnly = date -> {
                Calendar cal = Calendar.getInstance();
                cal.setTimeInMillis(date);
                int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
                return dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY;
            };
            
            DateValidator futureOnly = DateValidatorPointForward.now();
            DateValidator combinedValidator = CompositeDateValidator.allOf(
                Arrays.asList(futureOnly, weekdaysOnly)
            );
            
            CalendarConstraints constraints = new CalendarConstraints.Builder()
                .setValidator(combinedValidator)
                .setStart(MaterialDatePicker.todayInUtcMilliseconds())
                .build();
            
            MaterialDatePicker<Pair<Long, Long>> dateRangePicker = 
                MaterialDatePicker.Builder.dateRangePicker()
                    .setTitleText("Select vacation dates")
                    .setCalendarConstraints(constraints)
                    .build();
            
            dateRangePicker.addOnPositiveButtonClickListener(selection -> {
                Long startDate = selection.first;
                Long endDate = selection.second;
                
                // Calculate number of days
                long daysDiff = TimeUnit.MILLISECONDS.toDays(endDate - startDate) + 1;
                
                SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd", Locale.getDefault());
                String dateRangeText = dateFormat.format(new Date(startDate)) + 
                    " - " + dateFormat.format(new Date(endDate));
                
                dateRangeButton.setText(dateRangeText);
                selectedDatesText.setText(daysDiff + " days selected");
            });
            
            dateRangePicker.show(getParentFragmentManager(), "date_range_picker");
        });
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-google-android-material--material

docs

buttons-and-selection.md

feedback-and-communication.md

index.md

input-and-forms.md

layout-and-containers.md

navigation-components.md

pickers-and-selection.md

theming-and-styling.md

tile.json