Categories
discuss

Swiper observer random delay

I’m building a thumbnail gallery with a slider feature using Swiper (v6.5.1). By default, the slider is hidden, and when the user clicks one of the images, the slider must be displayed showing the clicked image. Once the slider is open, the user can click a Close button to hide it and return to the thumbnail gallery.

This is the code I’m using:

JS:

var swiper;

swiper = new Swiper( '.gallery-slider .swiper-container', {
    loop: true,
    observer: true,
    observeParents: true,
    autoplay: {
        delay: 3000,
        disableOnInteraction: false,
    },
    navigation: {
        nextEl: '.swiper-next',
        prevEl: '.swiper-prev',
    },
});

document.querySelectorAll( '.gallery-thumbnails a[data-slide]' ).forEach( item => {
    item.addEventListener( 'click', function( e ) {
        e.preventDefault();
        window.scrollTo( 0, 0 );
        document.querySelector( '.gallery-thumbnails' ).style.display = 'none';
        document.querySelector( '.gallery-slider .swiper-container' ).style.display = 'block';
        var slideno = $( this ).data( 'slide' );
        swiper.slideTo( slideno + 1, false, false );
    });
});

document.querySelector( '.gallery-slider .close' ).addEventListener( 'click', function( e ) {
    e.preventDefault();
    document.querySelector( '.gallery-slider .swiper-container' ).style.display = 'none';
    document.querySelector( '.gallery-thumbnails' ).style.display = 'flex';
});

CSS:

.gallery-slider .swiper-container {
    display: none;
}

HTML:

<div class="gallery-thumbnails">
    <div class="gallery-image"><a href="" data-slide="0"><img src="thumb0.jpg" /></a></div>
    <div class="gallery-image"><a href="" data-slide="1"><img src="thumb1.jpg" /></a></div>
    <div class="gallery-image"><a href="" data-slide="2"><img src="thumb2.jpg" /></a></div>
    <div class="gallery-image"><a href="" data-slide="3"><img src="thumb3.jpg" /></a></div>
    ....
</div>

<div class="gallery-slider">
    <div class="swiper-container">
        <div class="swiper-prev">previous</div>
        <div class="swiper-next">next</div>
        <div class="close">close</div>
        <div class="swiper-wrapper">
            <div class="swiper-slide"><img src="slide0.jpg" /></div>
            <div class="swiper-slide"><img src="slide1.jpg" /></div>
            <div class="swiper-slide"><img src="slide2.jpg" /></div>
            <div class="swiper-slide"><img src="slide3.jpg" /></div>
            ....
        </div>
    </div>
</div>

As you can see, the code uses Swiper’s observer feature, which reinitializes Swiper each time there’s a change in its style (like hide/show) or in its child elements (like adding/removing slides).

Everything works as expected the first time I click an image in the thumbnail gallery. But, if I hide the slider using the Close button and then click in a new image to open the slider again, the slider appears but is not responsive until some seconds have passed. The strange thing is that this delay is not always the same. Sometimes is almost instantaneous and sometimes more than x seconds have already passed when the slider begins to respond to clicks in the next and previous buttons. One thing I noticed is that if I resize the window, the slider becomes responsive instantly.

I tried this code in the latest Chrome and Firefox with the same results.

Is this the expected behavior? Is the MutationObserver used by the observer feature not always listening to changes in the DOM? Am I initializing Swiper the wrong way?

This is the code for the Swiper observer:

https://github.com/nolimits4web/swiper/blob/master/src/modules/observer/observer.js

Answer

I found the culprit of the strange delay. I was passing a boolean as the second argument in the slideTo function instead of an integer, which is the correct type for it.

You must change the line:

swiper.slideTo( slideno + 1, false, false );

to:

swiper.slideTo( slideno + 1, 0, false );

Categories
discuss

Is Duration#toDays and Duration#toDaysPart redundant?

In Java 8, the Duration class offered the toDays method, returning a total number of days as a count of 24-hour chunks of time unrelated to calendar days.

In Java 9, the Duration class gained handy to…Part methods: toDaysPart, toHoursPart, toMinutesPart, toSecondsPart, toMillisPart, toNanosPart. I understand the need for the hours, minutes, etc. But I wonder about toDaysPart.

My question is:

➥ Will Duration#toDays and Duration#toDaysPart ever return different values for a particular Duration object?

Answer

In Java 11, these lines of source code in OpenJDK are exactly the same.

Duration#toDays:

public long toDays() {
    return seconds / SECONDS_PER_DAY;
}

Duration#toDaysPart

public long toDaysPart(){
    return seconds / SECONDS_PER_DAY;
}

As of Java 16, no indication is made as to which is deprecated or which is not. So…keep your eyes peeled for it, is the best advice I could give you here.

Categories
discuss

Android, Can’t access ViewModels from detached fragment

Every time I rotate my phone, it crashes and prints strange exception in SearchResultFragment no matter isDetached value is false:

java.lang.IllegalStateException: Can't access ViewModels from detached fragment

in this part of code:

private val searchViewModel: SearchViewModel by viewModel()

fun searchWord(word: String) {
        if(!isDetached) searchResultViewModel.searchWord(word)
}

which is called from another fragment SearchFragment:

searchResultFragment.searchWord(searchWord)

SearchResultFragment is added to SearchFragment in this way:

private val searchResultFragment = SearchResultFragment()

private fun addFragment() {
    val fragmentTransaction = childFragmentManager.beginTransaction()
    fragmentTransaction.add(R.id.searchContainer, searchResultFragment)
    fragmentTransaction.commit()
}

I’m using Koin to dependency injection. I’ll be grateful for all hints.

Answer

It’s hard to see exactly what’s going on without the full context.

The #1 problem I see (also mentioned by some other user(s) is that you appear to be calling a public method in a Fragment, from another Fragment. This is a red-flag, since you’re now coupling two lifecycle independent objects with each other (among other things).

If anything, the quick getaway from this is that you could instead rely on a shared view model that talks to both Fragments and can deliver the state you want.

In any case, since it’s hard to tell what is exactly going on, you could instead try something like this in your onCreateView(...)

 if (savedInstanceState == null) {
            supportFragmentManager.commit {
                replace(
                    R.id.searchContainer,
                    SearchResultFragment.newInstance(),
                    SearchResultFragment::class.java.simpleName
                )
            }
        }

newInstance() is implemented in SearchResultFragment as:

    companion object {
        fun newInstance() = SearchResultFragment()
    }

Next, and because we don’t know the full picture, you could instead try to see if Fragment Manager already has your fragment…

Something like this (pseudo-code…)

        if (savedInstanceState == null) {
            val newFragment = supportFragmentManager.findFragmentByTag(SearchResultFragment::class.java.simpleName) ?: SearchResultFragment.newInstance()
            
            supportFragmentManager.commit {
                replace(
                    R.id.simple_fragment_container,
                    newFragment,
                    SearchResultFragment::class.java.simpleName
                )
            }
        }

This way, you add the fragments with a Tag (just a String to identify them) and then you check if the fragment manager already has it and use that, instead of always creating a new instance.

Last but not least, this could be affected by a number of other (external) factors:

  1. The Version of Fragment/Appcompat/AndroidX/etc. you’re using (as you may be aware, Fragment Manager is not the best class in Android… so many fixes and changes happen all the time).

  2. The Android Version: Some Android APIs have had better “fragment luck” than others (especially before we had the separate artifacts to change the “fragment” versions).

  3. I don’t think Koin has anything to add to this, but make sure you’re at least using the latest stable.

  4. Perhaps the best you can do is rely on a ViewModel instead of all this if (!detached) {...} thing. You’re really fighting the framework and ridding along the FragmentManager, something not even Google dares to do sometimes…

Categories
discuss

How to decrypt an AES/CBC encrypted string in Kotlin?

I have this Python method on the server to encrypt a string into bytes (AES/CBC).

class AESCipher(object, key):
    def __init__(self, key): 
        self.bs = AES.block_size
        self.key = hashlib.sha256(key.encode()).digest()

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw.encode()))

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

The output of encrypt() is in bytes like this: b'PMgMOkBkciIKfWy/DfntVMyAcKtVsM8LwEwnTYE5IXY='

I would like to store this into database, and send it as string via API to Kotlin. And in there I would like to decrypt it via the same shared secret key.

  1. In what format do I save the bytes above into database?
  2. Once arrived in Kotlin client, how do I convert that string into ByteArray?

My theory is that I have to store the bytes as base64 string in the database. And on the other side I have to decode the string as base64 into bytes. Is this approach correct? Will the encryption/decryption work like this end-to-end with the code below?

    fun decrypt(context:Context, dataToDecrypt: ByteArray): ByteArray {
            val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
            val ivSpec = IvParameterSpec(getSavedInitializationVector(context))
            cipher.init(Cipher.DECRYPT_MODE, getSavedSecretKey(context), ivSpec)
            val cipherText = cipher.doFinal(dataToDecrypt)
            val sb = StringBuilder()
            for (b in cipherText) {
                sb.append(b.toChar())
            }   
            return cipherText
    }
    
    fun getSavedSecretKey(context: Context): SecretKey {
            val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
            val strSecretKey = sharedPref.getString("secret_key", "")
            val bytes = android.util.Base64.decode(strSecretKey, android.util.Base64.DEFAULT)
            val ois = ObjectInputStream(ByteArrayInputStream(bytes))
            val secretKey = ois.readObject() as SecretKey
            return secretKey
    }
    
    fun getSavedInitializationVector(context: Context) : ByteArray {
            val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
            val strInitializationVector = sharedPref.getString("initialization_vector", "")
            val bytes = android.util.Base64.decode(strInitializationVector, android.util.Base64.DEFAULT)
            val ois = ObjectInputStream(ByteArrayInputStream(bytes))
            val initializationVector = ois.readObject() as ByteArray
            return initializationVector
        }

UPDATE

I have tried to remove the Base64 to remove the memory overhead as suggested.

Python:

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return iv + cipher.encrypt(raw.encode())

So this is no longer possible.

enc = AESCipher('abc').encrypt("myLife")
value_to_save_in_db = enc.decode("utf8")

So I need to find a way to store the byte array directly in the database. I think I should be able to do this as blob. But some challenges remain as how to send the bytearray as part of JSON over the API to the android device. I think I have to convert it to Base64 string again. Not sure if I have gained anything in that case…

Answer

The following Kotlin code:

val decrypted = decrypt("blEOKMQtUbNOzJbvEkL2gNhjF+qQ/ZK84f2ADu8xyUFme6uBhNYqvEherF/RRO9YRImz5Y04/ll+T07kqv+ExQ==");
println(decrypted);

decrypts a ciphertext of the Python code. Here decrypt() is:

fun decrypt(dataToDecryptB64 : String) : String {

    // Base64 decode Python data
    val dataToDecrypt = Base64.getDecoder().decode(dataToDecryptB64)

    // Separate IV and Ciphertext
    val ivBytes = ByteArray(16)
    val cipherBytes = ByteArray(dataToDecrypt.size - ivBytes.size)
    System.arraycopy(dataToDecrypt, 0, ivBytes, 0, ivBytes.size)
    System.arraycopy(dataToDecrypt, ivBytes.size, cipherBytes, 0, cipherBytes.size)

    // Derive key
    val keyBytes = MessageDigest.getInstance("SHA256").digest("abc".toByteArray(Charsets.UTF_8))

    // Decrypt
    val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
    cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(keyBytes, "AES"), IvParameterSpec(ivBytes))
    val cipherText = cipher.doFinal(cipherBytes)

    return String(cipherText, Charsets.ISO_8859_1)
}

For this, the ciphertext was generated using the posted Python class AESCipher as follows:

plaintext = 'The quick brown fox jumps over the lazy dog'
cipher = AESCipher('abc')
ciphertext = cipher.encrypt(plaintext)
print(ciphertext.decode('utf8')) # Base64 string, which can be stored e.g. in a DB

I applied the originally posted Python implementation that derives the key using SHA256. However, if the key is derived from a password, for security reasons not SHA256 but a reliable key derivation function, e.g. Argon2 or PBKDF2, should be used.

The Kotlin code first Base64 decodes the Python data and then separates IV and the actual ciphertext. Then, the key is derived by generating the SHA256 hash of the password. Finally the data is decrypted.


The current Python code Base64 encodes the data so that it can be stored as a string in the DB. Alternatively, the Python code could be modified so that no Base64 encoding is performed, and the raw data can be stored (which requires less memory, Base64 overhead: 33%).
Depending on the solution chosen, the Kotlin code may or may not need to Base64 decode the data.

Categories
discuss

hide Top and Bottom Navigator on a specific screen inside Scaffold Jetpack Compose

I’m creating a simple app with bottom navigation and drawer.

I wrap all screens inside a Scaffold with topbar and bottom bar. I want to hide top bar and bottom bar on a specific screen. Does anyone know to how achieve that

here is the code for setting up navigation.

val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))

Scaffold(
    bottomBar = {
        AppBottomBar(navController)
    },
    topBar = {
        AppTopBar(scaffoldState)
    },
    drawerContent = {
        DrawerContent(navController, scaffoldState)
    },
    scaffoldState = scaffoldState
) {
    // ovoid bottom bar overlay content
    Column(modifier = Modifier.padding(bottom = 58.dp)) {
        AppNavigation(navController)
    }
}

AppNavigation contains NavHost for navigating to screens

Answer

for now, I can achieve that by checking current route to show or hide bottomBar, topBar. But I think there’s must be better solutions. The way I wrap all screens inside Scaffold might not right.

val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))

Scaffold(
    bottomBar = {
        if (currentRoute(navController) != "Example Screen") {
            AppBottomBar(navController)
        }
    },
    topBar = {
        AppTopBar(scaffoldState)
    },
    drawerContent = {
        DrawerContent(navController, scaffoldState)
    },
    floatingActionButton = {
        FloatingButton(navController)
    },
    scaffoldState = scaffoldState
) {
    // ovoid bottom bar overlay content
    Column(modifier = Modifier.padding(bottom = 58.dp)) {
        AppNavigation(navController)
    }
}

@Composable
public fun currentRoute(navController: NavHostController): String? {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    return navBackStackEntry?.arguments?.getString(KEY_ROUTE)
}
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..