Kotlin var lazy init - delegates

I want to do a lazy initialization for a var property. Since by lazy is restricted only to val properties I have to write something like this:
private var currentContextProvider: ContextProvider? = null
get() {
if (field == null) {
field = DefaultContextProvider()
}
return field
}
And now I have to deal with those pointless nullability-aware calls: currentContextProvider?.getContext() or alternatively currentContextProvider!!.getContext()
Am I doing something wrong?

Instead of making it nullable, you can decide to initialise it with some default value, which on first access will be replaced with the lazily calculated value:
private val noInit = "noinit"
var currentContextProvider: String = noInit
get() = if (field == noInit) {
synchronized(this) {
return if (field == noInit) "lazyinit" else field
}
} else field
(I've replaced the ContextProvider with String)
Custom Delegate
The following implements a custom delegate reusing the former solution. It can be used just like lazy() by defining var currentContextProvider: ContextProvider by LazyMutable { DefaultContextProvider() }
class LazyMutable<T>(val initializer: () -> T) : ReadWriteProperty<Any?, T> {
private object UNINITIALIZED_VALUE
private var prop: Any? = UNINITIALIZED_VALUE
#Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return if (prop == UNINITIALIZED_VALUE) {
synchronized(this) {
return if (prop == UNINITIALIZED_VALUE) initializer().also { prop = it } else prop as T
}
} else prop as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
synchronized(this) {
prop = value
}
}
}

I needed a lazy delegated property that initializes and caches when you get the property, but allows you to set it to null to remove that cached result (and re-initialize() it when you get it again).
Thanks to the above answer for the code so I could tweak it.
#Suppress("ClassName")
class lazyNullCacheable<T>(val initializer: () -> T) : ReadWriteProperty<Any?, T> {
private object UNINITIALIZED_VALUE
private var prop: Any? = UNINITIALIZED_VALUE
#Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return if (prop == UNINITIALIZED_VALUE || prop == null) {
synchronized(this) {
initializer().also { prop = it }
}
} else prop as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
synchronized(this) {
prop = value
}
}
}
Usage :
var prop: String? by lazyNullCacheable {
"string"
}
prop // get prop
prop = null // when you're done using it and want to recalculate / cache it next time
prop // get prop, it will initialize() and cache again
Functionally equivalent to doing something like this (but this is uglier imho)
var _prop: Type? = null
val prop: Type
get() = _prop ?: run {
_prop = Type()
_prop!!
}
prop // get result
_prop = null // clear cache
prop // next get will recalculate it

class LazyMutable<T>(
val initializer: () -> T,
) : ReadWriteProperty<Any?, T> {
private val lazyValue by lazy { initializer() }
private var newValue: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>) =
newValue ?: lazyValue
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
newValue = value
}
}
usage:
var foo by LazyMutable { "initial value" }
(doesn't support nullable foo)

Test:
class LazyVarTest {
#Test
fun testLazyVar() {
var testVar: String by lazyVar { "initial" }
assertEquals("initial", testVar)
testVar = "test"
assertEquals("test", testVar)
}
#Test
fun testNullableLazyVar() {
var testVar: String? by lazyVar { "initial" }
assertEquals("initial", testVar)
testVar = "test"
assertEquals("test", testVar)
testVar = null
assertEquals(null, testVar)
}
}
Implementation:
fun <T> lazyVar(initializer: () -> T) = LazyVar(initializer)
class LazyVar<T>(initializer: () -> T) : ReadWriteProperty<Any?, T> {
private object initialValue
var isSet = false
private val lazyValue by lazy { initializer() }
private var value: Any? = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): T =
synchronized(this) {
if (!isSet) return lazyValue
return value as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =
synchronized(this) {
this.value = value
isSet = true
}
}

Related

Widget for weather app how to get LocationTracker

I would like to make simple widget for my weather app, to show local temperature. My question is: how to get the LocationTracker in my widget class?
class Widget: GlanceAppWidget() {...}
I found the solution, I use fusedLocationProviderClient
//client
val locationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
So, my method get location:
> //get location
private suspend fun getCurrentLocation(
context: Context,
locationClient: FusedLocationProviderClient
): Location? {
val hasAccessFineLocationPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val hasAccessCoarseLocationPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) ||
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
if (!hasAccessCoarseLocationPermission || !hasAccessFineLocationPermission || !isGpsEnabled) {
return null
}
return suspendCancellableCoroutine { cont ->
locationClient.lastLocation.apply {
if (isComplete) {
if (isSuccessful) {
cont.resume(result)
} else {
cont.resume(null)
}
return#suspendCancellableCoroutine
}
addOnSuccessListener {
cont.resume(it)
}
addOnFailureListener {
cont.resume(null)
}
addOnCanceledListener {
cont.cancel()
}
}
}
}

LiveData Transformation not getting triggered

I subscribed to ids and search in the ui but i wasn't getting any results so i stepped through with the debugger and found out that the transformation is not getting triggered after the first time. So when i call setIds the first time ids gets updated but for every call after the first one the transformation won't trigger. Same goes for the search.
Any ideas what might possible go wrong?
class MyViewModel : ViewModel() {
private val repository = Repository.sharedInstance
var recentRadius: LiveData<List<RecentRadius>>?
var recentRoute: LiveData<List<RecentRoute>>?
init {
recentRadius = repository.recentRadius()
recentRoute = repository.recentRoute()
}
private val idsInput = MutableLiveData<String>()
fun setIdsInput(textId: String) {
idsInput.value = textId
}
val ids: LiveData<List<String>> = Transformations.switchMap(idsInput) { id ->
repository.ids(id)
}
private val searchInput = MutableLiveData<Search>()
fun setSearchInput(search: Search) {
searchInput.value = search
}
val search: LiveData<SearchResult> = Transformations.switchMap(searchInput) { search ->
when (search.type) {
SearchType.ID -> repository.id(search)
SearchType.RADIUS -> repository.radius(search)
SearchType.ROUTE -> repository.route(search)
}
}
}
The most common reason why transformation don't get triggered is when there is no Observer observing it or the input LiveData is not getting changed.
Below example illustrates use of map when observer is attached in the activity.
Activity
class MainActivity : AppCompatActivity() {
lateinit var mBinding : ActivityMainBinding
private val mViewModel : MainViewModel by lazy {
getViewModel { MainViewModel(this.application) }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.vm = mViewModel
// adding obeserver
mViewModel.videoName.observe(this, Observer<String> { value ->
value?.let {
//Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
}
}
ViewModel with map
class MainViewModel(val appContext : Application) : AndroidViewModel(appContext) {
private val TAG = "MainViewModel"
var videoData = MutableLiveData<VideoDownload>()
var videoName : LiveData<String>
init {
// Update the data
videoName = Transformations.map(videoData) { "updated : "+it.webUrl }
}
fun onActionClick(v : View) {
// change data
videoData.value = VideoDownload(System.currentTimeMillis().toString())
}
fun onReActionClick(v : View) {
// check data
Toast.makeText(appContext, videoName.value, Toast.LENGTH_LONG).show()
}
}
ViewModel with switchMap
class MainViewModel(val appContext : Application) : AndroidViewModel(appContext) {
private val TAG = "MainViewModel"
var videoData = MutableLiveData<VideoDownload>()
var videoName : LiveData<String>
init {
// Update the data
videoName = Transformations.switchMap(videoData) { modData(it.webUrl) }
}
private fun modData(str: String): LiveData<String> {
val liveData = MutableLiveData<String>()
liveData.value = "switchmap : "+str
return liveData
}
fun onActionClick(v : View) {
// change data
videoData.value = VideoDownload(System.currentTimeMillis().toString())
}
fun onReActionClick(v : View) {
// check data
Toast.makeText(appContext, videoName.value, Toast.LENGTH_LONG).show()
}
}
for me, it was because the observer owner was a fragment. It stopped triggering when navigating to different fragments. I changed the observer owner to the activity and it triggered as expected.
itemsViewModel.items.observe(requireActivity(), Observer {
The view model was defined as a class property:
private val itemsViewModel: ItemsViewModel by lazy {
ViewModelProvider(requireActivity()).get(ItemsViewModel::class.java)
}
If you really want it to be triggered.
fun <X, Y> LiveData<X>.forceMap(
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MutableLiveData<Y>()
this.observeForever {x->
if (x != null) {
result.value = mapFunction.invoke(x)
}
}
return result
}

Android webview image upload

developed a webview app, I have an option to upload image (input type = "file"). In the browser functions normally, but within the webview, it does not. I would like some help to resolve this problem.
because you not post any code, take a look of my code.
It allow webview to upload image from camera or galery
class HandlingWebview(){
private var mFilePathCallback: ValueCallback<Array<Uri>>? = null
private var mCameraPhotoPath: String? = null
companion object {
const val CHOOSE_FILE_REQUEST_CODE = 9685
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
initObserver()
initView()
}
private val permissionUtils: PermissionUtils by lazy {
PermissionUtils(
this,
trackingService,
getString(R.string.rationale_storage),
Constant.RC_PERMISSIONS_DOWNLOAD_DOCS,
Constant.PERMISSIONS_DOWNLOAD_DOCS
)
}
private fun initView() {
binding.webView.settings.apply {
javaScriptEnabled = true
loadWithOverviewMode = true
useWideViewPort = true
domStorageEnabled = true
}
binding.webView.setOnKeyListener(object : View.OnKeyListener {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (event?.action == KeyEvent.ACTION_DOWN) {
val webView = v as WebView
when (keyCode) {
KeyEvent.KEYCODE_BACK -> if (webView.canGoBack()) {
webView.goBack()
return true
}
}
}
return false
}
})
binding.webView.apply {
loadUrl(url)
}
binding.webView.webViewClient = object : WebViewClient() {
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
super.doUpdateVisitedHistory(view, url, isReload)
}
}
binding.webView.webChromeClient = object : WebChromeClient() {
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
if (permissionUtils.isAllPermissionAllowed()) {
// Double check that we don't have any existing callbacks
startActivityChooser(fileChooserParams, filePathCallback)
} else observePermissionResult(permissionUtils.build().asLiveData(), fileChooserParams, filePathCallback)
return true
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) = permissionUtils.onRequestPermissionsResult(requestCode, permissions, grantResults)
private fun startActivityChooser(
fileChooserParams: WebChromeClient.FileChooserParams?,
filePathCallback: ValueCallback<Array<Uri>>?
) {
mFilePathCallback?.onReceiveValue(null)
mFilePathCallback = filePathCallback
activity?.packageManager?.let {
var takePictureIntent: Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent?.resolveActivity(it) != null){
var photoFile: File? = null
try {
photoFile = createImageFile()
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath)
} catch (ex: IOException) {
// Error occurred while creating the File
Timber.i("Unable to create Image File $ex")
}
// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.absolutePath
takePictureIntent.putExtra(
MediaStore.EXTRA_OUTPUT,
Uri.fromFile(photoFile)
)
} else {
takePictureIntent = null
}
}
val contentSelectionIntent = Intent(Intent.ACTION_GET_CONTENT)
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE)
contentSelectionIntent.type = "image/*"
val intentArray: Array<Intent?> = takePictureIntent?.let { arrayOf(it) } ?: arrayOfNulls(0)
val chooserIntent = Intent(Intent.ACTION_CHOOSER)
chooserIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
startActivityForResult(chooserIntent, CHOOSE_FILE_REQUEST_CODE)
}
}
private fun initObserver() {}
private fun observePermissionResult(
permissionResult: LiveData<Event<PermissionUtils.Companion.PermissionResult>>,
fileChooserParams: WebChromeClient.FileChooserParams?,
filePathCallback: ValueCallback<Array<Uri>>?
) {
permissionResult.observe(viewLifecycleOwner) { event ->
event?.getContentIfNotHandled()?.let {
when (it) {
is PermissionUtils.Companion.PermissionResult.Denied -> {
// pass
}
is PermissionUtils.Companion.PermissionResult.Granted -> {
// pass
}
is PermissionUtils.Companion.PermissionResult.AllGranted -> {
startActivityChooser(fileChooserParams, filePathCallback)
}
}
}
}
}
override fun useCustomBackEvent(): Boolean = true
override fun onBackEvent() {
destroyWebView()
super.onBackEvent()
}
private fun destroyWebView() {
binding.llParent.removeAllViews()
binding.webView.apply {
clearHistory()
clearCache(true)
onPause()
removeAllViews()
destroyDrawingCache()
destroy()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
CHOOSE_FILE_REQUEST_CODE -> {
var results: Array<Uri>? = null
if (resultCode == Activity.RESULT_OK && mFilePathCallback!= null) {
if (data == null) { // take photo from camera
if (mCameraPhotoPath != null) results = arrayOf(Uri.parse(mCameraPhotoPath))
} else { // image picker
data.dataString?.let { results = arrayOf(Uri.parse(it)) }
}
}
mFilePathCallback?.onReceiveValue(results)
mFilePathCallback = null
}
}
super.onActivityResult(requestCode, resultCode, data)
}
#Throws(IOException::class)
private fun createImageFile(): File? {
// Create an image file name
val timeStamp: String = getTodayDateString()
val imageFileName = "JPEG_" + timeStamp + "_"
val storageDir: File = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
)
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
)
}
}

Cannot call a function from init block because of val property

I'd like to initialize my class's properties.
Because I'm using heavily the functional elements of Kotlin, I'd like to put these initializations to well named functions, to increase readability of my code.
The problem is that I cannot assign a val property, if the code is not in the init block, but in function which is called from the init block.
Is it possible to take apart initialization of a class, to different functions, if the properties are vals?
Here is the code:
val socket: DatagramSocket = DatagramSocket()
val data: ByteArray = "Cassiopeiae server discovery packet".toByteArray()
val broadcastAddresses: List<InetAddress>
init {
socket.broadcast = true
val interfaceAddresses = ArrayList<InterfaceAddress>()
collectValidNetworkInterfaces(interfaceAddresses)
collectBroadcastAddresses(interfaceAddresses)
}
private fun collectValidNetworkInterfaces(interfaceAddresses: ArrayList<InterfaceAddress>) {
NetworkInterface.getNetworkInterfaces().toList()
.filter { validInterface(it) }
.forEach { nInterface -> nInterface.interfaceAddresses.toCollection(interfaceAddresses) }
}
private fun collectBroadcastAddresses(interfaceAddresses: ArrayList<InterfaceAddress>) {
broadcastAddresses = interfaceAddresses
.filter { address -> address.broadcast != null }
.map { it.broadcast }
}
Of course it's not compiling, because collectBroadcastAddresses function tries to reassign the broadcastAddresses val. Although I don't want to put the code of this function to the init block, because it's not obvious what the code is doing, and the function name tells it very nicely.
What can I do in such cases? I'd like to keep my code clean, this is the most important point!
One way of approaching the problem is to use pure functions to initialize fields:
class Operation {
val socket = DatagramSocket().apply { broadcast = true }
val data: ByteArray = "Cassiopeiae server discovery packet".toByteArray()
val broadcastAddresses = collectBroadcastAddresses(collectValidNetworkInterfaces())
private fun collectValidNetworkInterfaces() =
NetworkInterface.getNetworkInterfaces().toList()
.filter { validInterface(it) }
.flatMap { nInterface -> nInterface.interfaceAddresses }
private fun validInterface(it: NetworkInterface?) = true
private fun collectBroadcastAddresses(interfaceAddresses: List<InterfaceAddress>) {
interfaceAddresses
.filter { address -> address.broadcast != null }
.map { it.broadcast }
}
}
Notice how the socket field initialization uses apply extension.
I often find it useful to extract collection manipulation routines into extension methods:
class Operation {
val socket = DatagramSocket().apply { broadcast = true }
val data: ByteArray = "Cassiopeiae server discovery packet".toByteArray()
val broadcastAddresses = NetworkInterface.getNetworkInterfaces()
.collectValidNetworkInterfaces { validInterface(it) }
.collectBroadcastAddresses()
private fun validInterface(it: NetworkInterface?) = true
}
fun Iterable<InterfaceAddress>.collectBroadcastAddresses(): List<InetAddress> =
filter { address -> address.broadcast != null }.map { it.broadcast }
fun Enumeration<NetworkInterface>.collectValidNetworkInterfaces(isValid: (NetworkInterface) -> Boolean = { true }) =
toList()
.filter { isValid(it) }
.flatMap { nInterface -> nInterface.interfaceAddresses }

How do I apply a default IComparable<T> in a Linq OrderBy clause

I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}

Resources