AdaptUI came as a solution to reduce context switches between code and various XML files. Along with the ability to sligthly tweak some XML layouts, drawables or styles without copying data just in order to change an attribute or two.
With AdaptUI it is possible to create reusable, configurable and tweakable views and drawables without leaving a single file in which they are defined. Combine that with the build-in layout preview that comes with latest versions of Android Studio and you can have a truly single-file components on Android. A single-file component reduces context switches, allows inspection and modification of view building blocks meanwhile giving an immediate feedback with the help of the preview. It keeps the loop running without the breaks.
the problem is actually that SwiftUI is a complexity-hiding abstraction. No a complexity-hiding abstraction, if it does not work, go to the native view layer directly. Almost disappearing framework? Can be used to create view, create and update or just update?... no, this is not true, we cannot update without being created, well, in theory, we could use an Element and pass it a view, but this is crazy
Copying when needed a minor change, non extensible, no configurable, values are limited to be provided by xml,
make a padding @dimen/content_padding + @dimen/additional_padding
is impossible leads to generating
more layers of indirection
Comment a line in XML - error, cannot do it
Moreover, with AdaptUI does not make commitment.. It does not require a special compiler - all it is using is Kotlin code. All it operates on - native Android views. It does provide conveniences on top of native views, but does not restrict access. You still can access a view underneath, create own elements, or extensions on elements.
AdaptUI has been inspired by Flutter, Combine and SwiftUI. Important difference from Flutter is the direction is which elements are build - in Flutter you wrap target views with customization views, like Padding, SizedBox, etc, so in the end initial view becomes wrapped under multiple layers of customization - like a cabbage. It hurts readability and discoverability. Views also expect to receive all its arguments in constructor, which leaves little Flutter is like a pockemon cabbage. Target (conceptually important) views are hidden by multiple pockemon layers with weird names and zero discoverability. In order to find what it is inside (what view) it holds you need to unwrap the layers, which make a lot of noise whilst you do that. Easier to copy code, see how it behaves, just comment related code lines, no need to modify structure. The same is re
Another things that {{positively}} distinguishes AdaptUI is AdaptUI does not come with any state system, it can work with any.
easily discoverable customizations ease of creating a quick sketch with primary views, then gradually updating them to match final design ability to comment parts of customization (flutter complicated, XML does not allow comments for attributes)
// creates a Text `ViewElement` that wraps `TextView`
Text()
Text()
is an extension function of a ViewFactory
. Elements should be created
inside ViewFactory
context, for example:
// TextView would be returned for the `ViewFactory.createView`
val textView = ViewFactory.createView(context) {
Text()
}
AdaptUI provides these view elements out-of-box (adding a new one is a metter of creating a new extension function):
Text()
->TextView
HScroll()
->HorizontalScrollView
HStack()
->LinearLayout
withHORIZONTAL
orientationImage()
->ImageView
Pager()
->androidx.viewpager.ViewPager
(androidx.viewpager
should be added to your dependencies list explicitly)Progress()
->ProgressBar
with indeterminate progressSpacer()
->View
, an element that can be used only as a child ofHStack
andVStack
(LinearLayout
), specifieslayout_weight
TextInput()
->EditText
View()
->View
VScroll()
->ScrollView
VStack()
->LinearLayout
withVERTICAL
orientationZStack()
->FrameLayout
Those are the building blocks defined directly in AdaptUI. But any android view or view-group can be represented as one
VStack {
Element(::CheckBox) // ViewElement<CheckBox, LinearLayout.LayoutParams>
// the same as
Element { context -> CheckBox(context) }
}
Each element also holds information about LayoutParams
, so it is possible to configure them in a
type-safe manner
VStack {
Text() // ViewElement<TextView, LinearLayout.LayoutParams>
.layoutWeight(1F)
Image() // ViewElement<ImageView, LinearLayout.LayoutParams>
.layoutGravity(Gravity.trailing.end)
// is available only for LinearLayout.LayoutParams
Spacer()
}
AdaptUI follows a simple naming patterns for customization. For example, all layout customizations
that affect LayoutParams start with layout*
-
layoutWeigth
, layoutGravity
, layoutMargin
etc. Individual elements prefix customization
functions with its name. Those for example are some customizations available for the Text
element:
text(CharSequence)
textGravity(Gravity)
textColor(Int)
textHideIfEmpty()
- and others
All of the customizations that affect dimensions (width, height, padding, margin, etc) are already in DIP (density independent pixels), there is no need to convert them explicitly to pixels
Text()
.padding(16) // 16.dp
.layoutMargin(vertical = 8) // 8.dp
.layout(FILL, 128) // 128.dp
When building static layouts all normal code flows are working as expected
VStack {
if (someCondition) {
Text("Some condition was met")
} else {
HStack {
Image(error)
Text("Condition was not met")
}
}
// creates 10 Texts
(0..9).forEach {
Text("$it")
}
}
Static means here - after view is build it is not going to be modified if someCondition
boolean changes. This condition is checked only when view is built initially. In order to
build dynamic layouts - adapt
items can be used.
Elements can still be referenced as regular objects:
VStack {
// it is referenced, but still added to the layout
val image = Image() // ViewElement<ImageView, LinearLayout.LayoutParams>
Text("Button-like")
.padding(16)
.background(Colors.accent)
.onClick {
// modify element (automatically will render by posting to the main thread)
image.background(Colors.primary)
// or render explicitly immediately in this thread
image.background(Colors.primary).render()
// or access view directly
// here we can access the view because it has been already initialized
image.view.setBackgroundColor(Colors.primary)
}
}
Pass elements and items around, allowing wide usage Preview in layout Use as an argument to achieve dynamism
Drive a customer/user crazy -> change position on click (easy list manipulation)