LatteKit


Source link: https://github.com/maannajjar/lattekit

What is LatteKit

It's a framework for building Android UI in Kotlin code by using the concept of virtual views and reactive data flow, the goal is to reduce boilerplate while retaining the same Android layout constructs.

Quick Example

package io.lattekit.helloworld import android.app.Activity import android.os.Bundle import android.view.View import android.widget.EditText import io.lattekit.annotation.Bind import io.lattekit.plugin.css.declaration.css import io.lattekit.render import io.lattekit.view.LatteView  class MainActivity : Activity() {

  override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

render("<io.lattekit.helloworld.MyApp />")
  
}
 
}
  open class MyApp : LatteView() {

  @Bind("@id/myText") var myText : EditText? = null;

init {

css(""" 

.question {
 font-size: 20sp; font-weight: bold;  
}
 

.input {
 font-size: 14sp; margin-top:8dp;  width: match_parent; 
}
 

.answer {
 font-size: 20sp; font-weight: bold; margin-top: 10dp; color: #00AADE; 
}
 

  """)

// or: css("com.my.package/file.css") 
  
}

override fun layout() = xml(""" 

  <LinearLayout padding="30dp" orientation="vertical"> 

<TextView text="What's your name?" class="question"/> 

<EditText id="@+id/myText" hint="Type your name here" 

 onTextChanged=${
{
 notifyStateChanged() 
}

}
 class="input"/> 

<TextView text=${
"Hello ${
myText?.text
}
"
}
 

 visibility=${
if (myText?.text?.toString() == "") View.GONE else View.VISIBLE
}
 class="answer"/> 

  </LinearLayout> 
 """)  
}

The above layout code is not "string", it's layout representation. The actual XML code will be parsed and compiled by the LatteKit gradle plugin. The use of string interpolation is to make it easier to reference scope variables in the layout. You can use any kotlin expression to set a property value, including lambdas.

Other Samples

For more samples, view the samples at lattekit-samples folder. But before viewing the sample, please read the core concept below to understand the sample better.

How It Works

1- Virtual Views (LatteViews)

The core concept of LatteKit is to define virtual views. A virtual view is subclass of LatteView that defines its own layout. The layout may contain native Android views and other virtual views. It can also receive properties passed to it from the rendering view. For example

open class MyApp : LatteView() {

  var currentUser : User? = null;
  override fun layout() = xml(""" 

  <LinearLayout padding="30dp" orientation="vertical"> 

<views.UserDetailsView user=${
currentUser
}
 /> 

  </LinearLayout> 
 """) 
}
  open class UserDetailsView : LatteView() {

  @Prop var user : User? = null;
  // Or @Prop("customPropertyName")

override fun layout() = xml(""" 

  <LinearLayout orientation="horizontal" paddingTop="10dp" paddingBottom="10dp"> 

<ImageView src=${
user?.avatarUrl
}
 layout_width="50dp" layout_height="50dp" /> 

<TextView text=${
user?.username
}
 layout_gravity="center_vertical" layout_height="wrap_content"/> 

  </LinearLayout> 
 """) 
}

Here, MyApp & UserDetailsView are virtual views. MyApp contains UserDetailsView which expects property user. When the native view tree is built, UserDetailsView will be replaced by the actual layout. Properties are automatically assigned to variables with @Prop annotation. In the above example, MyApp passed currentUser as user propertiy in UserDetailsView.

2- Data Binding & Maintaining View States

As demonstrated in the quick example, you can use any variable you want in your layout code. Just use string interpolation to pass any value to properties. You can use lambdas and reference other views in the layout code. Any variables used in the layout is considered state variable. If some variable's value changes, you will need to notify the view to update its layout by calling notifyStateChanged(). When notified, the virtual view will update its layout and all property changes will flow through the virtual layout tree. In the previous example, if MyApp changes currentUser for any reason (for example due to API call), all it needs to do is call notifyStateChanged() after the change. This will update user property in UserDetailsView which will then update its layout. Calling notifyStateChanged() will always ensure that the layout tree reflects the correct state of the notified view.

Binding Views

If you need to have reference to views from your layout code, you can use @Bind annotation. This is similar to what you do when you call findViewById. By default, the annotation will look for a view with the same id as the variable name. But you can specify the id in the annotation too:

@Bind var saveButton : Button? = null; // Or // @Bind("@id/saveButton")  saveButton : Button? = null;  override fun layout() = xml("""  <Button id="@+id/saveButton" /> """) 

You can also refernece those views inside your layout, as demonstrated in the first example.

ListView/RecyclerView/ViewPager

Those views have special treatment inside the layout definition. LatteKit will create an adapter for you, all you have to do is provide the views it should render. You can basically think of it as a for loop that iterates through data set (it's not for loop though, it just implements an Adapter behind the scene). For example:

open class MyListView : LatteView() {

 var myData : List<Any> = listOf(...)

override fun layout() = xml(""" 

  <ListView data=${
myData
}
 layout_width="match_parent" layout_height="match_parent"> 
<views.AdItemView when=${
{
 it : Any -> it is AdData 
}

}
 />

   
<views.FoodItemView when=${
{
 it : Any -> it is FoodData 
}

}
 defaultView="true" /> 

  </ListView> 
 """) 
}
 open class FoodItemView : LatteView() {
  @Prop("model") var foodDeatils: FoodData? = null;
 override fun layout() = xml("""<TextView text=${
foodDeatils?.title
}
 />""") 
}
 open class AdItemView : LatteView() {
  @Prop("model") var adDetails: AdData? = null;
 override fun layout() = xml(""" .... """) 
}

Here, the ListView will render myData dataset. The dataset contains two different kinds of objects: AdData and FoodData. Each needs to have different view (FoodItemView or AdItemView). The adapter will determine which template to use by calling the lambda specified via when property. If none of the templates matche, it will fallback to the first template that has defaultView=true. It will then render the template and pass the property model which will contain the corrospending item in the myData.

The template will use model property passed by the adapter.

Lifecycle

If you need to initialize your component, or need to initialize Android views outside the layout. You can override onViewCreated. Also, if you need to need to perform any action before the view is removed from the layout tree, you can override onViewWillDetach().

open class MyListView : LatteView() {
  var myData : List<Any> = emptyList();
  ..  ..  override fun onViewCreated() {

ApiManager.getFeed()

.subscribeOn(Schedulers.io())

 .observeOn(AndroidSchedulers.mainThread())

 .subscribe {
  response ->

  myData = response

  notifyStateChanged()

 
}
  
}
  override fun onViewWillDetach() {

backgroundMusic?.stop();
  
}

 override fun layout() = xml(""" 

  <ListView data=${
myData
}
 layout_width="match_parent" layout_height="match_parent" dividerHeight="0"> 
<views.AdItemView when=${
{
 it is AdData 
}

}
 />

   
<views.FoodItemView when=${
{
 it is FoodData 
}

}
 defaultView="true" /> 

  </ListView> 
 """) 
}

CSS Styling

There are two ways to use CSS styling. 1) Local CSS, which are CSS delcarations that are defined inside LatteView. Those declarations don't cascade down to other virtual views. or 2) Global css, which are defined in a separate css file. The css file should be just inside any java package. Here how to use them:

open class MyView : LatteView() {

  @Bind("@id/myText") var myText : EditText? = null;

init {

 // Global CSS

css("com.my.package/myfile.css")

// Local CSS 

css(""" 

.myclass {
 padding: 20dp; 
}
 
 """)
  
}

override fun layout() = xml(""" 

  <LinearLayout class="myclass"> 

  </LinearLayout> 
 """)  
}

CSS styling is not 100% complete, most used properties are already implemented. I'll keep adding more support in later releases. I'll expand this section later to explain what special cases of CSS properties. For the meaning time, you can view more css examples at lattekit-samples

Getting Started

1- Add the gradle plugin class path to buildscript

buildscript {

  dependencies {

classpath 'io.lattekit.tools:gradle-plugin:0.9.3'
  
}
 
}
 

2- Apply the plugin

// The plugins must be in that order apply plugin: 'com.android.application' apply plugin: 'lattekit'  apply plugin: 'kotlin-android' 

3- Add runtime lib to dependency to build.gradle


 compile 'io.lattekit:lattekit-core:0.9.3' 

4- Define your virtual views as explained above

it's important that LatteView subclasses are declared open class. Gradle plugin will throw an error if it sees layout code inside non-open (final) class.

5- To render LatteView within activity:

import io.lattekit.render
class MyActivity : Activity() {

  override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

// Note: props here are passed as map and not in xml

render("<com.package.MyView />",props=mutableMapOf("title" to "MyApp"))
  
}
 
}

6- If you just want the native View object, you can call buildView().

var virtualView = Latte.render("<com.package.MyView />",props=mutableMapOf("title" to "MyApp"));
 var androidView = virtualView.buildView(myActivity,LayoutParams(..)) // You can later update props using: virtualView.props.put("prop",value);
 virtualView.notifyStateChanged();

7- To show LatteView as an activity form another LatteView

Latte.showActivity(this,"<com.package.UserProfile />", mutableMapOf("user" to user))

8- To show LatteView as a Dialog form another LatteView

Latte.showDialog(this,"<com.package.UserProfile />", mutableMapOf("user" to user))

License

The MIT License (MIT)  Copyright (c) 2016 Maan Najjar  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 

Resources

A layout that arranges other views in a circle.

You can use this library to create your calendar, customizing UI and behaviour.

Features

  • Custom header (should be implemented by the user);
  • Custom sub view (month name by default);
  • Custom weekly days view;
  • Custom date view;
  • Possibility to implement selection on day click;
  • Possibility to implement weekly day calculation;
  • Receive updates of Calendar (with AUCalendar);
  • Every change to AUCalendar is notified and automatically refreshes UI;

A library to download Youtube video as mp3 files.

When we use startActivityForResult(), we have to receive result/data in onActivityResult().

This is very complex and inconvenience to us.

Simple, elegant, beautiful, material design based Android music player.

While developing features we get crashes and if device is not connected to logcat we miss the crash log. In worst case scenario we might not be able to reproduce the crash and endup wasting effort. This library captures all unhandled crashes and saves them locally on device. I found a problem with other libraries that they capture crashes and then uploads them to server and then crashes are shown in their online Dashboards but sometimes few crashes don't logged to server. That's the purpose of this library use it as a debug feature to capture crashes locally and immediately.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes