Just with several values? - ios

I want a Publisher similar to Just except it should emit multiple values, and then complete the stream.
Let's call it JustSeveral:
func fibonacci(_ number: Int) -> AnyPublisher<Int, Never> {
Future { ... }.eraseToAnyPublisher()
}
JustSeveral([293, 18, 104])
.flatMap { fibonacci($0) }
.sink { print($0) }
Is there a way to combine operators with Just to get the same thing, or do I need to build this Publisher myself?
Note: Any solution should be able to handle an arbitrary number of elements in the array, not just 3.
For example, if we had an uncollect operator, this would be trivial:
Just([293, 18, 104])
.uncollect { $0 }
.flatMap { fibonacci($0) }
.sink { print($0) }

Importing Combine extends Array with a computed property called publisher, which returns a Sequence<Array<Int>, Never>, which is a Publisher. So you can just do:
[293, 18, 104]
.publisher
.flatMap { fibonacci($0) }
.sink { print($0) }
However, Apple's API documentation blows chunks, and I don't think there's a page that documents Array.publisher.

Update: You should use the built-in .publisher method.
Original answer:
One solution is to make the publisher wait until there is a subscription and then emit values:
public struct JustSeveral<Value>: Publisher {
public init(_ outputs: [Value]) {
self.outputs = outputs
}
public var outputs: [Value]
public typealias Output = Value
public typealias Failure = Never
public func receive<Downstream: Subscriber>(subscriber: Downstream) where Downstream.Input == Output, Downstream.Failure == Failure {
let subject = PassthroughSubject<Value, Never>()
subject
.subscribe(subscriber)
outputs.forEach { subject.send($0) }
subject.send(completion: .finished)
}
}
Although this works, I don't know if it's the best solution since it only emits values upon a subscription. I'm not certain that that is how Just works.

Related

Ui is not updating from viewmodel kotlin flow

I am quite new in Android Flow and JetPack compose,
I am trying to update my UI when mutable state is being changed , but this is not calling our composable , here is my code
#Composable
fun Grid() {
val mainViewModel by viewModels<DashBoardViewModel>()
mainViewModel.getData()
when (val result = mainViewModel.mutableState.value) {
is Resource.Success -> {
LazyVerticalGrid(
cells = GridCells.Adaptive(100.dp)
) {
items(result.device.items.first().devices.count()) {
EachItem(it)
}
}
}
is Resource.Error -> { Text(text = result.message) }
Resource.Loading -> { CircularProgressIndicator() }
Resource.Empty -> {}
else -> { CircularProgressIndicator() }
}
}
ViewModel:
#HiltViewModel
class DashBoardViewModel #Inject constructor(
private val dashBoardRepository: DashBoardRepository
) : ViewModel() {
val mutableState = MutableLiveData<Resource>()
fun getData() = viewModelScope.launch {
flow {
emit(Resource.Loading)
try {
val mResponse = dashBoardRepository.getDevice()
emit(Resource.Success(mResponse))
} catch (e: Exception) {
e.printStackTrace()
emit(Resource.Error("Error"))
}
}.flowOn(Dispatchers.IO).collect {
mutableState.value = it
}
}
}
There are two problems in your code:
mainViewModel.mutableState.value gets only the current value from your mutable state. Your composable will not be notified when this value changes and thus it cannot reflect the change. If you want to use LiveData in your viewmodel, you have to use observeAsState() extension function which converts LivaData to State that can be automatically observed by composable function. Other option is to have (Mutable)State directly in your viewmodel. See this state explanation.
Your mainViewModel.getData() function will be called every time your Grid() function recomposes, which will be every time your mainViewModel.mutableState changes (once you observe it correctly). You definitely don't want that. Better solution would be to call getData() from your viewModel's init block, or, if you really need to call it from your composable function, use LaunchedEffect.
And, as a side note, the way you are creating flow and then collecting it into LiveData is really odd and unnecessary. You can do something like this instead:
fun getData() = viewModelScope.launch {
mutableState.value = Resource.Loading
try {
val mResponse = dashBoardRepository.getDevice()
mutableState.value = Resource.Success(mResponse)
} catch (e: Exception) {
e.printStackTrace()
mutableState.value = Resource.Error("Error")
}
}

How to pass a composable content parameter in data class

I need to pass a compose content parameter in data class. For example a button can render when added into this content.
data class ContentData {
val content: #Composable ()-> Unit
}
This is working but when I get the app background I am getting parcelable exception. How to solve this problem.
One possible explanation I think that will occur related with a parcelable error, happens if you try to pass such object between activities as extras through Intent. Consider not use Composable as parameters in objects. Instead, try to represent the parameters of your Composable with a model which contains the parameters.
// your compose function
#Composable
fun Item(content: String = "Default", padding: Dp){
// ...
}
// Ui Model which contains your data (instead of have a weird composable reference) as a parcelable.
data class ContentData(
val content: String = "Default",
val paddingRaw: Int = 0
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString().orEmpty(),
parcel.readInt()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(content)
parcel.writeInt(paddingRaw)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ContentData> {
override fun createFromParcel(parcel: Parcel): ContentData {
return ContentData(parcel)
}
override fun newArray(size: Int): Array<ContentData?> {
return arrayOfNulls(size)
}
}
}
// Example if you need the model between activities through the intent as an extra.
val data = ContentData("your content", 11)
val intent = Intent().apply {
putExtra("keyContentData", data)
}
//The way of get and use your model.
val contentData = intent.extras?.get("keyContentData") as ContentData
#Composable
fun ParentComponent(){
// ...
Item(
contentData?.content.orEmpty(),
contentData?.paddingRaw?.dp ?: 0.dp
)
// ...
}

Error Ambiguous reference to member 'indices' in SwiftUI

I am trying to iterate over all the dates I have received from my API and convert them into a usable format for my app. I am running into an error with this code
ForEach(dateList.indices, id: \.self) { date in
self.que = "";
for letter in dateList[date] {
if letter == "T" {
dateList[date] = self.que
return
}
else if letter == "-" {
self.que = self.que + "/"
}
else {
self.que = self.que + letter;
}
}
}
I am trying to have this iterate over each string I have in the dateList array and convert it into a format that is usable in my app. This format is going from 2020-02-28T03:32:44Z to 2020/02/28. I am getting the error "Ambiguous reference to member 'indices'" and I'm not sure what this means.
struct ForEach is only using for showing views:
/// A structure that computes views on demand from an underlying collection of
/// of identified data.
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ForEach<Data, ID, Content> where Data : RandomAccessCollection, ID : Hashable { ... }
for example you can use it to show rows of some List
List {
ForEach(dateList.indices, id: \.self) { dateIndex in Text("\(self.dateList[dateIndex])") }
}
it's not about computing some variable like que, you should extract this computing into some function and call it from onAppear for example. Here is a little example of ForEach and forEach difference:
struct SomeLoopData: View {
#State var dates = ["2020/02/28", "2020/02/29"]
var body: some View {
List {
ForEach(dates.indices) { index in Text("\(self.dates[index])") }
.onAppear {
self.compute()
}
}
}
func compute() {
dates.indices.forEach { index in
print(dates[index])
}
}
}

RxJava- Turn Observable into Iterator, Stream, or Sequence

I know this breaks a lot of Rx rules, but I really like RxJava-JDBC and so do my teammates. Relational databases are very core to what we do and so is Rx.
However there are some occasions where we do not want to emit as an Observable<ResultSet> but would rather just have a pull-based Java 8 Stream<ResultSet> or Kotlin Sequence<ResultSet>. But we are very accustomed to the RxJava-JDBC library which only returns an Observable<ResultSet>.
Therefore, I am wondering if there is a way I can turn an Observable<ResultSet> into a Sequence<ResultSet> using an extension function, and not do any intermediary collection or toBlocking() calls. Below is all I have so far but my head is spinning now trying to connect push and pull based systems, and I cannot buffer either as the ResultSet is stateful with each onNext() call. Is this an impossible task?
import rx.Observable
import rx.Subscriber
import java.sql.ResultSet
fun Observable<ResultSet>.asSequence() = object: Iterator<ResultSet>, Subscriber<ResultSet>() {
private var isComplete = false
override fun onCompleted() {
isComplete = true
}
override fun onError(e: Throwable?) {
throw UnsupportedOperationException()
}
override fun onNext(rs: ResultSet?) {
throw UnsupportedOperationException()
}
override fun hasNext(): Boolean {
throw UnsupportedOperationException()
}
override fun next(): ResultSet {
throw UnsupportedOperationException()
}
}.asSequence()
I'm not sure that's the easiest way to achieve what you want but you can try this code. It converts an Observable to an Iterator by creating a blocking queue and publishing all events from the Observable to this queue. The Iterable pulls events from the queue and blocks if there're none. Then it modify its own state depending on received current event.
class ObservableIterator<T>(
observable: Observable<T>,
scheduler: Scheduler
) : Iterator<T>, Closeable {
private val queue = LinkedBlockingQueue<Notification<T>>()
private var cached: Notification<T>? = null
private var completed: Boolean = false
private val subscription =
observable
.materialize()
.subscribeOn(scheduler)
.subscribe({ queue.put(it) })
override fun hasNext(): Boolean {
cacheNext()
return !completed
}
override fun next(): T {
cacheNext()
val notification = cached ?: throw NoSuchElementException()
check(notification.isOnNext)
cached = null
return notification.value
}
private fun cacheNext() {
if (completed) {
return
}
if (cached == null) {
queue.take().let { notification ->
if (notification.isOnError) {
completed = true
throw RuntimeException(notification.throwable)
} else if (notification.isOnCompleted) {
completed = true
} else {
cached = notification
}
}
}
}
override fun close() {
subscription.unsubscribe()
completed = true
cached = null
}
}
You can use the following helper function:
fun <T> Observable<T>.asSequence() = Sequence { toBlocking().getIterator() }
The observable will be subscribed to when the sequence returned is called for iterator.
If an observable emits elements on the same thread it was subscribed to (like Observable.just for example), it will populate the buffer of the iterator before it gets a chance to be returned.
In this case you might need to direct subscription to the different thread with a call to subscribeOn:
observable.subscribeOn(scheduler).asSequence()
However, while toBlocking().getIterator() doesn't buffer all results it could buffer some of them if they aren't consumed timely by the iterator. That might be a problem if a ResultSet gets somehow expired when the next ResultSet arrives.

Generic function parameter in Swift points to invalid memory

I'll start with an example setup.
class Parent {
let parentProperty = 1
}
class Child : Parent {
let childProperty = 2
}
class Test {
func testMethod<T : Parent>(data: T) {
// (llbd) print data
}
}
let child = Child()
let test = Test()
// (lldb) print child
test.testMethod(child)
I paused execution on places marked with comment "(lldb) print ..." and executed "print child/data" command in debugger console in Xcode.
Output from said commands are listed below.
// print child
(RingRingTests.Child) $R0 = 0x00007fcb886459a0 {
RingRingTests.Parent = {
parentProperty = 1
}
childProperty = 2
}
// print data
(RingRingTests.Child) $R1 = 0x0000000115a93818 {
RingRingTests.Parent = {
parentProperty = 140512143366480
}
childProperty = 4294967299
}
The child and data variables obviously point to different location in memory. (being that data point to some invalid memory)
This seems to be like the most basic setup for generic function is swift, nevertheless it is failing.
I guess I'm doing something fundamentally wrong. Can somebody point me to the right direction? Thank you.
I've tried to replace your proposed lldb-tests with plain old debug output and all works fine.
class Parent: CustomDebugStringConvertible {
let parentProperty = 1
var debugDescription: String { return "\(parentProperty)" }
}
class Child: Parent {
let childProperty = 2
override var debugDescription: String { return "\(parentProperty), \(childProperty)" }
}
class Test {
func testMethod<T : Parent>(data: T) {
print(data)
}
}
let child = Child()
let test = Test()
print(child) // 1, 2
test.testMethod(child) // 1, 2
Some optimisation can screw lldb-tests, but optimisation should not be done for debug builds.
In other words, my answer is "it's actually all right with your code, but maybe something is wrong with your tests".

Resources