1

I am creating my first kotlin multiplataform project, and i'm having some difficulty to use the kotlin flow on swift. I created the models, server data requests as common file using kotlin flow and ktor, the the view model and ui layers i am creating as native. So, i have no experience with swift development, and beyond that i' having a lot of trouble to use flow on swift view model. Searching for an answer to my problem i found a class described as CommonFlow, which aims to be used as a common code for both languages(kotlin, swift, but i'm having an error that gives me little or no clue as to why it happens or, probably, it's just my lack of dominion with xcode and swift programming:

So this is the code part that xcode points the error: enter image description here Obs: sorry about the image i thought that maybe this time it would be more descriptive

And this its all i got from the error

Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5928ff8)

MY Ios ViewModel:

class ProfileViewModel: ObservableObject {
    private let repository: ProfileRepository
    
    init(repository: ProfileRepository) {
        self.repository = repository
    }
    
    @Published var authentication: Authetication = Authetication.unauthenticated(false)
    @Published var TokenResponse: ResponseDTO<Token>? = nil
    @Published var loading: Bool = false
    
    func authenticate(email: String, password: String) {
        DispatchQueue.main.async {
            if(self.isValidForm(email: email, password: password)){
                self.repository.getTokenCFlow(email: email, password: password).watch{ response in
                    switch response?.status {
                    
                        case StatusDTO.success:
                            self.loading = false
                                let token: String = response!.data!.accessToken!
                                SecretStorage().saveToken(token: token)
                                self.authentication = Authetication.authenticated
                                break;
                            
                            case StatusDTO.loading:
                                self.loading = true
                            break;
                                
                            case StatusDTO.error:
                                print("Ninja request error \(String(describing: response!.error!))}")
                                break;
                                
                            default:
                                break
                    }
                }
            }
        }
    }
    
    private func isValidForm(email: String, password: String) -> Bool {
        var invalidFields = [Pair]()
        if(!isValidEmail(email)){
            invalidFields.append(Pair(first:"email invalido",second: "email invalido"))
        }
            
        if(password.isEmpty) {
            invalidFields.append(Pair(first:"senha invalida",second: "senha invalida"))
        }
            
        if(!invalidFields.isEmpty){
            self.authentication = Authetication.invalidAuthentication(invalidFields)
            return false
        }
        return true
    }
    
    private func isValidEmail(_ email: String) -> Bool {
        let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"

        let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
        return emailPred.evaluate(with: email)
    }
    
}

class Pair {
    let first: String
    let second: String
    init(first:String, second: String) {
        self.first = first
        self.second = second
    }
}
enum Authetication {
    case invalidAuthentication([Pair])
    case authenticated
    case persistentAuthentication
    case unauthenticated(Bool)
    case authenticationFailed(String)
    
}

The repository methods:

override fun getToken(email: String, password: String): Flow<ResponseDTO<Token>> = flow {
        emit(ResponseDTO.loading<Token>())
        try {
            val result = api.getToken(GetTokenBody(email, password))
            emit(ResponseDTO.success(result))
        } catch (e: Exception) {
            emit(ResponseDTO.error<Token>(e))
        }
    }

@InternalCoroutinesApi
    override fun getTokenCFlow(email: String, password: String): CFlow<ResponseDTO<Token>> {
        return wrapSwift(getToken(email, password))
    }

The Class CFLOW:

@InternalCoroutinesApi
class CFlow<T>(private val origin: Flow<T>): Flow<T> by origin {
    fun watch(block: (T) -> Unit): Closeable {
        val job = Job()
        onEach {
            block(it)
        }.launchIn(CoroutineScope(Dispatchers.Main + job))

        return object: Closeable {
            override fun close() {
                job.cancel()
            }
        }
    }
}

@FlowPreview
@ExperimentalCoroutinesApi
@InternalCoroutinesApi
fun <T> ConflatedBroadcastChannel<T>.wrap(): CFlow<T> = CFlow(asFlow())

@InternalCoroutinesApi
fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)

@InternalCoroutinesApi
fun <T> wrapSwift(flow: Flow<T>): CFlow<T> = CFlow(flow)

1 Answer 1

-1

There is an example of using flows in KampKit

https://github.com/touchlab/KaMPKit

I will paste an excerpt from NativeViewModel (iOS)

class NativeViewModel(
    private val onLoading: () -> Unit,
    private val onSuccess: (ItemDataSummary) -> Unit,
    private val onError: (String) -> Unit,
    private val onEmpty: () -> Unit
) : KoinComponent {

    private val log: Kermit by inject { parametersOf("BreedModel") }
    private val scope = MainScope(Dispatchers.Main, log)
    private val breedModel: BreedModel = BreedModel()
    private val _breedStateFlow: MutableStateFlow<DataState<ItemDataSummary>> = MutableStateFlow(
        DataState.Loading
    )

    init {
        ensureNeverFrozen()
        observeBreeds()
    }

    @OptIn(FlowPreview::class)
    fun observeBreeds() {
        scope.launch {
            log.v { "getBreeds: Collecting Things" }
            flowOf(
                breedModel.refreshBreedsIfStale(true),
                breedModel.getBreedsFromCache()
            ).flattenMerge().collect { dataState ->
                _breedStateFlow.value = dataState
            }
        }



This ViewModel is consumed in swift like this:
lazy var adapter: NativeViewModel = NativeViewModel(
    onLoading: { /* Loading spinner is shown automatically on iOS */
        [weak self] in
        guard let self = self else { return }
        if (!(self.refreshControl.isRefreshing)) {
            self.refreshControl.beginRefreshing()
        }
    },
    onSuccess: {
        [weak self] summary in self?.viewUpdateSuccess(for: summary)
        self?.refreshControl.endRefreshing()
    },
    onError: { [weak self] error in self?.errorUpdate(for: error)
        self?.refreshControl.endRefreshing()
    },
    onEmpty: { /* Show "No doggos found!" message */
        [weak self] in self?.refreshControl.endRefreshing()
    }
)

In short, the flow is kept wrapped in kotlin mp land and leveraged in iOS by using traditional callback interfaces.

Sign up to request clarification or add additional context in comments.

2 Comments

Skie produces many errors when working on iOS
@RAULQUISPE what kind of "many errors"?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.