GUI programming is about presentation of data and handling complex interactions. A GUI programmer typically is not concerned with UX and design but rather with their implementation. And this is not an easy task. To understand why let us explore the challenges in GUI programming. I will categorize challenges to improve clarity.
Perhaps the most obvious is the visual category. It includes complex layouts, styles, animations and transitions. In my experience, implementing layouts and styles can often be annoying and Sisyphean but not that difficult. Expressing sophisticated animations and transitions, on the other hand, is hard.
Another challenge are constraints within and between GUI elements. The prime example are forms. Certain fields must accept only certain kinds of content. The content of some fields might influence other fields. Depending on the content of all fields the form itself must react accordingly. Add in asynchronous requests as well as error feedback and you have got yourself a hairy problem.
Responsiveness includes optimistic updates, prefetching, caching and feedback to the user about the GUI’s current state. The goal of responsiveness is to minimize perceived loading periods and to communicate the GUI’s current state to the user. Optimistically updating the GUI means acting as if the asynchronous API’s response was successful. The tricky part is reverting when the actual response is unsuccessful. Handling further optimistic updates while the API request has not finished yet is tricky, too. Prefetching makes it possible to immediatly show some data when the user switches to another view. When to prefetch and how to provide the data to the next view are not trivial questions. Caching can be very hard and caching in GUI programming is no different.
Even with the above optimizations handling loading and failure states is required. You cannot assume always-on connectivity. Depending on the requirements many components must be able to show a loading indicator and error feedback. The error feedback will most likely depend on the kind of received error. For some errors allowing the user to retry by himself makes sense. Being able to instrument the GUI (or its API) to observe how it handles slow loading and error situations is extremely helpful but not easy to achieve. As you can see, error handling can be complex.
The next challenge is one of the hardest: asynchrony. Asynchrony is involved in user interactions such as clicks, handling signals from devices (think IoT), managing animations and, most importantly, exchanging data with remote or latency-intensive APIs. Coordinating multiple interdependent requests while making sure that errors are handled correctly can be nerve-wracking. On top of that, asyncrony opens the door to race conditions which are notoriously hard to prevent.
State managment and change propagation are probably the hardest challenges. They are overarching as they are involved in almost every part of the GUI implementation and deeply influence how all the previously mentioned challenges are approached. The goal is to properly reflect data changes throughout the GUI. Stale data might be displayed if a case was missed. Counteracting staleness might lead to overshooting and updating the GUI when it was not needed. A lot of boilerplate code might be required to get the update logic just right. Undo/redo functionality makes things even trickier.
Over the last years there has been considerable innovation in terms of tooling, frameworks, libraries and paradigms—and there are no signs of slowing down. Yet, even with the ideal tools in our hands these challenges remain. And this is why GUI programming is exciting.