A scheduler week view and a Gantt chart

Yesterday I argued that a framework should give you more than the OS does. Here’s the test case. Nobody ships a calendar view you can fill with your own appointments, or a Gantt chart. Not iOS, not Android, not Flutter, not React Native, not Kotlin Multiplatform, not .NET MAUI. Every one of them leaves you with a date picker and a blank screen. If your app books haircuts, plans shifts or tracks a project, you write that screen yourself, once per platform if you went native, or you license a commercial component and pay for it every year. Codename One ships both, in the box, free.

Codename One now ships a scheduler, a Gantt chart with resource leveling, and Material 3 date and time pickers. Codename One draws its own UI, so these are the same component, pixel for pixel, on iOS, Android, the desktop and the web, and they take their look from your CSS.

A Gantt chart with the critical path highlighted

A scheduler in ten lines

The model and the UI are separate. com.codename1.scheduling holds the data with no UI, so the same classes work in your server code. com.codename1.ui.scheduler draws it.

ZoneId zone = ZoneId.systemDefault();
long today = TimeMath.epochDay(System.currentTimeMillis(), zone);

DefaultScheduleModel model = new DefaultScheduleModel();
model.addItem(new ScheduleItem("review", "Design review",
        TimeMath.at(today, 13 * 60, zone), TimeMath.at(today, 14 * 60 + 30, zone))
        .setDetail("Room 2"));

Scheduler scheduler = new Scheduler(model);
scheduler.setView(Scheduler.View.WEEK);

Form form = new Form("Schedule", new BorderLayout());
form.add(BorderLayout.CENTER, scheduler);
form.show();

The scheduler in week view

Scheduler.View gives you DAY, WORK_WEEK, WEEK, MONTH, AGENDA and RESOURCE_DAY, and there’s a resource timeline for “who’s busy when” screens. Items can be dragged and resized with snapping (setSnapMinutes), the working hours come from a WorkCalendar, and setZone lets a clinic in Berlin see the schedule in Berlin time while the phone is in Lisbon.

The scheduler month view
Month.
The scheduler showing a day per resource
A day per resource.
The scheduler day view in dark mode
Dark mode.

A Gantt chart that knows who’s doing the work

The Gantt component reads a ProjectModel: tasks with durations, dependencies between them and the people assigned to them. It draws bars you can drag, dependencies you can link by dragging from one bar to another, a baseline to compare against, collapsible groups and the critical path, which is the chain of tasks where any delay moves the end date.

A leveled plan with the load per person

The part I’m proudest of is resource leveling. A plan that looks fine on paper often has the same developer on two tasks in the same week. ResourceLeveler finds those overallocations and proposes a fix, and you decide whether to apply it:

WorkCalendar calendar = new WorkCalendar();
ProjectModel project = new ProjectModel(calendar, System.currentTimeMillis());
project.addResource(new ScheduleResource("dev", "Developer"));
project.addTask(new GanttTask("api", "API", 3 * calendar.getMillisPerDay()).assign("dev"));
project.addTask(new GanttTask("screens", "Screens", 2 * calendar.getMillisPerDay()).assign("dev"));
project.update();

if (!ResourceLeveler.findOverallocations(project).isEmpty()) {
    LevelingResult proposal = ResourceLeveler.level(project);
    long slip = proposal.getFinishAfter() - proposal.getFinishBefore();
    if (proposal.isChanged() && Dialog.show("Level resources",
            proposal.getChangedTasks().size() + " tasks move; the project ends "
                    + slip / TimeMath.DAY + " days later.", "Apply", "Cancel")) {
        proposal.apply();
    }
}

The proposal tells you which tasks move and how much later the project ends, before anything changes. The load histogram under the chart shows the result: no more stacked bars for one person.

flowchart LR P[ProjectModel
tasks, links, people] --> S[ProjectScheduler
dates from work calendar] S --> O{ResourceLeveler
overallocated?} O -->|no| G[Gantt draws it] O -->|yes| L[LevelingResult
what moves, new end date] L -->|user applies| G
A Gantt chart with baseline bars
Against the baseline.

Material 3 pickers, styled your way

The date, time and date and time pickers now come as Material 3 dialogs: a month grid with a year list and a field to type the date, and a clock dial with a keyboard mode. Apps on the Android Material native theme get them with no code. Any other theme asks for them with one constant:

#Constants {
    pickerStyle: "material";
}

Existing Picker code doesn’t change. Branding them is a few rules on the PickerMaterial UIIDs:

PickerMaterialDay.selected {
    background-color: #0f766e;
    color: #ffffff;
}

PickerMaterialDial.selected {
    background-color: #0f766e;
    color: #ffffff;
}
The Material 3 date picker
The date picker.
The Material 3 time picker clock dial
The clock dial.
The date picker restyled with teal CSS rules
Restyled in CSS.
The time picker in dark mode
Dark mode.

This is the lightweight argument in one picture. A native date picker looks the way Apple or Google decided it looks this year. These look the way you decide, on every device, and a field that needs the platform’s own dialog can still ask for it with setUseLightweightPopup(false).

Slate: a booking app you can ship

Slate is our new full-stack template: an appointment booking app and its server, in one Maven project. Salons, clinics, tutors, personal trainers: anyone who sells time. Generate it from the Initializr under your own name and package. The source is MIT-0, so you owe nothing and credit nobody.

Slate showing free times for a service
Free times.
Slate confirming a booking with a deposit
Confirm, with a deposit.
A provider's week in Slate
The provider's week.
The owner's staffing plan as a Gantt chart, with each provider's load under it
The week's staffing, as a Gantt chart.
The owner previewing booking rules on a real day
Rules, previewed on a real day.

It has three roles, and the server decides which screens an account sees:

  • Clients see free times by day or week, with times that clash with their own phone calendar marked. A hold keeps the slot while they book, they can pay a deposit, and the booking lands in their calendar with a reminder that moves if they reschedule. There’s a waitlist for full days.
  • Providers manage hours and time off, mark appointments complete or no-show, and sync their calendars from the phone, Google, Microsoft or an ICS address. Event titles never leave the device; Slate only needs to know when they’re busy.
  • Owners get a dashboard, a board for the day, a staffing plan for the week, the service catalog and booking rules they can preview on a real day before saving them.

The hard part of a booking product is the slot engine, and that’s where most of Slate’s server lives. It weighs providers, rooms, buffers between appointments, travel time, gap rules and the client’s own calendar together, and the hold prevents two clients from getting the same slot.

Slate’s owner dashboard on the desktop

It runs on iOS, Android, the desktop and the web, and the backend runs on SQLite, PostgreSQL, MySQL, or MariaDB.

Slate runs on the scheduler and the Gantt chart

Slate’s calendar screens are the components in this post. The provider’s day and week are Scheduler views, and the owner’s day board is the RESOURCE_DAY view with a column per provider. The owner’s staffing plan is a Gantt chart: a row per provider, a bar per shift filled as far as it’s booked, and the resource load under it showing who has room this week. The implementor’s guide walks through how a week of schedules becomes a project model.

Learn more

The Scheduling and Gantt charts chapter covers every view, renderer, listener and CSS hook, including printing with SchedulePrinter. The pickers are under “Material date and time pickers” in the components chapter, and Slate has a user’s guide and an implementor’s guide.

If you build planning or booking software, I’d like to know which view you need that isn’t there yet.