This library is a layer upon the android platform, trying to lift out bad design design decisions, and other annoyances. including the tremendous boilerplate code that is required in android programming.
old support libs: below 0.0.20 AndroidX : 0.0.20 and above
Below version 0.0.20 is the old support libray versions, 0.0.20 and above will be using androidX Hotfixes and bug fixing will be provided for the older as well as the newer part for some time.
The point about this library is that google is developing android as a platform, which means to allow developers to create everything, which does not necessarily translate to "easy to program for"
That is what this library tries to do, gap the difference between the platform and the common sense expectation about using it.
Using androids databinding is quite boilerplate ish, and comes with quite a lot of annoying work; For example, creating a DataBind'ed activity
Vanilla
class ExampleActivity: Activity {
private var binding: ExampleActivityBinding? = null
override fun onCreate(savedInstanceState: Bundle?){
binding = ExampleActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
//use binding, which is at this point optional
binding?.textview.text = "someExample"
}
}
With Csense
class ExampleDatabindingActivity : BaseDatabindingActivity<ExampleActivityBinding>() {
override fun createBinding(): InflaterFunctionSimple<ExampleActivityBinding> =
ExampleActivityBinding::inflate
override fun useBinding() {
//use binding, which is not null anymore;
binding.exampleActivityTextview.text = "example"
}
}
Launching activities
Vanilla
startActivity(Intent(this, MainActivity::class.java))
With Csense
startActivity(MainActivity::class)
Finishing an Activity safely (all kinds of Activity)
Vanilla
runOnUiThread{
finish()
}
With Csense
safeFinish()
Pushing a new fragment to the current stack (inside of a FragmentActivity)
Vanilla
val fragment: Fragment = ...
val containerId : Int = R.id.some_container_id
supportFragmentManager?.let{
val transaction = beginTransaction()
transaction.replace(containerId,fragment)
transaction.addToBackStack("a uniq name")
transaction.commit()
}
With Csense
val fragment: Fragment = ...
val containerId : Int = R.id.some_container_id
pushNewFragmentTo(containerId, fragment)
Performing transactions of the fragment manager
Vanilla
//simple commit
supportFragmentManager?.let {
val transaction = beginTransaction()
//do the work, and remeber to call comit
// could span many lines.
transaction.commit()
}
//commitNow
supportFragmentManager?.let {
val transaction = beginTransaction()
//do the work, and remeber to call comit
// could span many lines.
transaction.commitNow()
}
With Csense
supportFragmentManager?.transactionCommit {
//safe inlined call where commit will be called for us no matter what we do in here.
}
supportFragmentManager?.transactionCommitNow {
//safe inlined call where commitNow will be called for us no matter what we do in here.
}
(see https://developer.android.com/training/permissions/requesting#make-the-request, changed to only ask for camera) Vanilla
val MY_PERMISSIONS_REQUEST_CAMERA = 4567
fun askForCamera() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
ActivityCompat.requestPermissions(thisActivity,
arrayOf(Manifest.permission.CAMERA),
MY_PERMISSIONS_REQUEST_CAMERA)
} else {
// Permission has already been granted
//use camera
}
}
// ...later in the activity
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
MY_PERMISSIONS_REQUEST_CAMERA -> {
// If request is cancelled, the result arrays are empty.
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
//use camera
} else {
//could not use camera
}
}
//...
}
}
With Csense in BaseActivity(1)
use(PermissionEnum.Camera, {
//can use camera
}, {
//could not use camera
})
Or more explicit
(Does not need to be a BaseActivity, as long as the PermissionHandler is setup correctly, and receives the "onRequestPermissionsResult" callback)
PermissionEnum.Camera.use(permissionHandler, this, {
//use camera
}, {
//could not use camera
})
In the case of using kotlin coroutines / suspend
(Does not need to be a BaseActivity, as long as the PermissionHandler is setup correctly, and receives the "onRequestPermissionsResult" callback)
PermissionEnum.Camera.useSuspend(permissionHandler, this, {
//use camera, we are in a suspend function
}, {
//could not use camera, we are in a suspend function
})
Making a view visible / invisible or gone
Vanilla
val view : View = ...
view.visibility = View.GONE
view.visibility = View.VISIBLE
view.visibility = View.INVISIBLE
With Csense
val view : View = ...
view.gone()
view.visible()
view.invisible()
Querying for the visibility of a view
Vanilla
val view : View = ...
val isGone = view.visibility == View.GONE
val isVisible = view.visibility == View.VISIBLE
val isInvisible = view.visibility == View.INVISIBLE
With Csense
val view : View = ...
val isGone = view.isGone
val isVisible = view.isVisible
val isInvisible = view.isInvisible
Change visibility based on boolean
Vanilla
val view : View = ...
val bool = ...
if (bool) {
view.visibility = View.VISIBLE
} else {
view.visibility = View.GONE
}
With Csense
val view : View = ...
val bool = ...
view.visibleOrGone(bool)
Or Just toggling it
Vanilla
val view : View = ...
val bool = view.visibility == View.GONE
if (bool) {
view.visibility = View.VISIBLE
} else {
view.visibility = View.GONE
}
With Csense
val view : View = ...
val bool = ...
view.toggleVisibilityGone(bool)
Making a view disabled / enabled
Vanilla
val view : View = ...
//enable
view.isEnabled = true
view.isClickable = true
//disable
view.isEnabled = false
view.isClickable = false
With Csense
val view : View = ...
view.enable()
view.disable()
Where even ViewGroups are handled (in Csense), where as the vanilla example does not handle ViewGroups at all.
whenever you want some kind of selection regarding 1 item, or you want to be able to toggle between them, then theres is the
- SingleSelectionHandler (only 1 selected at a time)
- ToggleSelectionViewHandler (every one can be selected or deselected)
To most peoples surprise calling "setContentView" with a layout id, causes the main thread to go and read from the disk,parse and deinflate an xml layout, which takes quite a while. However there is a possible solution, which is to use the "AsyncLayoutInflater", however it does have some very weird drawbacks;
Vanilla
With Csense (inside of a BaseSplashActivity)
Commonsense ships with a very flexible, simple logging component called "L".
The extensions for views, activities ect are all using this logging, and for good reason. It supports removing all logging functions (functions that does something with the log statements) It supports customizing logging functions (say you want to send the logs to a server) It supports disabling all logging no matter what It supports a special "production" logging channel, so some logging can be done even if production apps turn off logging It does not require a tag in most cases , since the classname is already there (reduces boilerplate)
The internal library uses it.
The are a lot of extensions for using it, both from views, activities and so on. These extensions are called "logDebug", "logWarning" , "logError", "logProduction" and does not take a tag, because that is the calling objects true code name.
So for an activity this would look like
Vanilla
class MyActivity : Activity {
fun test() {
Log.d(TAG,"my message")
}
companion object {
val TAG = "MyActivity"
}
}
With Csense
class MyActivity : Activity {
fun test() {
logDebug("my message")
}
}
Turning off logging
Vanilla
- Not supported out of the box (hacks via proguard exists but have sideeffects)
With Csense
L.isLoggingAllowed(false)
Piping Logging (to eg a server)
Vanilla
- not supported out of the box
With Csense
//add a new listener for the debug "channel"
L.debugLoggers.add{ tag: String, message:String, throwable : Throwable? ->
///handling logging here.
}
The project is accessible though jcenter, so there should be a jcenter() in the repositories.
It is recommended to store the version 1 place only, such as;
buildscript {
ext.commonsenseVersion = "0.0.19.11"
}
Then you can start importing each of the sub modules. a full list is:
implementation "com.commonsense.android.kotlin:base:$commonsenseVersion"
implementation "com.commonsense.android.kotlin:system:$commonsenseVersion"
implementation "com.commonsense.android.kotlin:widgets:$commonsenseVersion"
implementation "com.commonsense.android.kotlin:tools:$commonsenseVersion"
implementation "com.commonsense.android.kotlin:prebuilt:$commonsenseVersion"
//for tests
testImplementation "com.commonsense.android.kotlin:test:$commonsenseVersion"
If you want to use all modules, then there is an "all" which contains all the submodules; in gradle:
implementation "com.commonsense.android.kotlin:all:$commonsenseVersion"
You will still have to add the test manually.
The artifacts can be viewed at: https://bintray.com/tvede-oss/Commonsense-android-kotlin
see Intro https://github.com/Tvede-dk/CommonsenseAndroidKotlin/blob/master/documentation/Intro.md