1

I'm so frustrated on searching what is the equivalent of this java code on swift:

public abstract class BaseApiSubscriber<T> extends Subscriber<T> {
   private WeakReference<MvpView> mvpViewWeakReference;
   private WeakReference<BasePresenter> basePresenterWeakReference;

   public BaseApiSubscriber(BasePresenter basePresenter, MvpView mvpView) {
     this.basePresenterWeakReference = new WeakReference<>(basePresenter);
     this.mvpViewWeakReference = new WeakReference<>(mvpView);
   }

   @Override
   public void onCompleted() {
   }

   @Override
   public void onError(Throwable e) {
      //handle generic errors here, call also the mvpView to handle generic responses on UI
   }

   @Override
   public void onNext(T t) {
   }

Basically here, I'm extending Subscriber so all of the generic response of API is handled on a single file. This works on my java (android), but I can't find how to make this work on swift. I tried searching about extensions, protocols but it seems they can't be extended. I did try to search, but I don't know the keyword for this, I try to code blindly (hoping it will work) but I can't. Maybe just a keyword, basic sample, or an explanation on this. Am I doing it right, right? Ready also for the downvotes because I just can't post a good code on swift. It is not even close to this.

Update: Somehow I got close to it, thanks to @luk2302, but then how can I implement this? Here's my code:

class BaseSubscriber: ObserverType {

typealias E = Response

func on(_ event: Event<Response>) {
    switch event {
    case .next(let _):
        print("Successing")
        break
    case .error(let error):
        print("Erorring")

        if let serviceError = error as? ServiceError {
            print("service error: " + serviceError.errorDescription!)
        }

        print(error)
        break
    case .completed:
        print("Completing")
        break
    }
}
}

Then I need to call this from here:

let base = BaseSubscriber()
repo.login(param: loginParam).subscribe(
     //Ive tried this:
     //base.on: {
     //}
     //but got an syntax error maybe hahahaha
)

What do you call this? So I can search and read about it. Thank you.

Update 2: Thanks to @Cristik, I've managed to do it, and by passing a closures, I can now pass methods to do specific task per request. My updated code:

func baseSubscriber<T>(mvpView: BaseMvpView, onNext: @escaping (T) -> Void, onError: @escaping (Error) -> Void, onCompleted: @escaping () -> Void)  -> (RxSwift.Event<T>) -> Void {
return { [weak mvpView] event in
    switch event {
    case let .next(element):
        mvpView?.hideLoading()
        print("super next")
        onNext(element)

    case .completed:
        mvpView?.hideLoading()
        print("super completed")
        onCompleted()

    case let .error(error):
        mvpView?.hideLoading()
        print("super error")
        if let serviceError = error as? ServiceError {
            print("Service error: \(serviceError.errorDescription ?? "Something wrong")")
        } else {
            onError(error)
        }
    }
}

}

But this is different to my approach on java in which I can override the onError() method so in case I want to disregard the generic error handling, I can do it. How can I apply it to swift?

Update 3:

BaseMvpView.swift

protocol BaseMvpView: class {

   func showLoading(message: String)

   func hideLoading()
}

BaseTableViewController.swift

class BaseTableViewController: UITableViewController, BaseMvpView {

var indicator: UIActivityIndicatorView?

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

override func viewDidLoad() {
    super.viewDidLoad()

    indicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
    indicator?.frame = CGRect(origin: CGPoint.init(x: 0, y: 0), size: CGSize.init(width: 40, height: 40));
    indicator?.center = view.center
    view.addSubview(indicator!)
    indicator?.bringSubview(toFront: view)
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
}

func showLoading(message: String) {
    indicator?.startAnimating()
}

func hideLoading() {
    indicator?.stopAnimating()
}
}
Tenten Ponce
  • 2,436
  • 1
  • 13
  • 40
  • 3
    Something like https://stackoverflow.com/questions/24469913/how-to-create-generic-protocols-in-swift ? – luk2302 Feb 28 '18 at 12:24
  • Thank you, somehow I got close to it, but implementing it seems to be hard (for me, well I need to read more about swift), by the way, what do you call this? So I can read it by myself. Thank you again. – Tenten Ponce Feb 28 '18 at 12:57
  • Seems like you simply need to pass base: `.subscribe(base)`. – Valérian Feb 28 '18 at 13:56
  • @TentenPonce Can you explain the usage code a little more? E.g., what kind of class/enum is returned from `repo.login(...)`, and what does `.subscribe(...)` do/expect to receive? – aunnnn Feb 28 '18 at 16:54
  • Seems you're trying too hard to achieve this in an OOP manner, why do you need a dedicated subscriber class? ReactiveX philosophy is about reacting to events, and you have plenty of Rx operators that can help you do your job without having to write additional types. – Cristik Feb 28 '18 at 22:24
  • @aunnnn login return an `Observable`. On all of my calls, I do have a transformer that handles error and throw a specific error like the `ServiceError`. Ill update my question when I got to the office, sorry for not being clear – Tenten Ponce Feb 28 '18 at 23:34
  • @Cristik, I just want to handle the error on a single file, actually this works on my android (rxjava). I have a generic dialog that displays when being triggered from the `BaseSubscriber`. All of my activities is a subclass of a parent activity which holds the showing of the generic error dialog. I dont know if Im doing it wrong, and if, what would be the proper way to handle this? I just dont want to right all generic dialog that must be called when an error happens on all apis. Hope you understand my point. Thank you – Tenten Ponce Feb 28 '18 at 23:48
  • @Cristik, for clarification, Im passing the base `mvpView` so it can be called on the `BaseUbscriber`. If you have any clarification, just tell me so I can clear it out. Thank you again :) – Tenten Ponce Feb 28 '18 at 23:52
  • @TentenPonce maybe I'm missing something, but still don't understand why you'd need a dedicated class for this. – Cristik Mar 01 '18 at 05:29
  • @Cristik Ill override the onNext on each call and do specific tasks for them. I just want to have a base to handle the common responses of apis. – Tenten Ponce Mar 01 '18 at 05:32

1 Answers1

1

You don't need a dedicated class for this, seems the only thing you need is a subscriber that weakly references the view and the presenter, and that handles common event handling logic.

Since an RxSwift subscriber can also be a closure with one event argument, you could implement a free function that will create it:

func customSubscriber<T>(mvpView: MvpView, presenter: BasePresenter) -> (RxSwift.Event<T>) -> Void {
    return { [weak mvpView, weak presenter] event in
        switch event {
        case let .next(element):
            print("Received \(element)")

        case .completed:
             print("Done")

        case let .error(error):
            if let serviceError = error as? ServiceError {
                print("Service error: \(serviceError)")
            } else {
                print("Some error: \(error)")
            }
        }
    }
}

which you can use it like this:

myObserver.subscribe(customSubscriber(mvpView: someMvpView, presenter: somePresenter))

RxSwift paradigms are centered around closures: you create observers from closures, subscribers are closures, operators work with closures. Also in Swift other types like structs and enums (aka value types) are preferred over classes (aka reference types), they just work better in most of the contexts.

Update Based on the latest snippet, seems you also want to allow different event handling based on the receiver. This can be elegantly achieved by using a protocol with default implementations.

protocol Subscribable: class {
    associatedtype DataType

    func onNext(_ data: DataType)
    func onCompleted()
    func onError()
}

extension Subscribable where Self: BaseMvpView {
    func onNext(_ data: DataType) {
        hideLoading()
        print("super next")
    }

    func onCompleted() {
        hideLoading()
        print("super completed")
    }

    func onError() {
        hideLoading()
        print("super error")
        if let serviceError = error as? ServiceError {
            print("Service error: \(serviceError.errorDescription ?? "Something wrong")")
        }
    }
}

// BaseMvpView will get all above methos implemented, child classes can implement their own version.
extension BaseMvpView: Subscribable { 
    typealias DataType = DataTypeThatIsUsedByMvpView
}

// the subscriber function got simplifier as it no longer needs
// the callback parameters, as those are not part of the protocol
// this also makes the function more flexible as it's not tied to a
// concrete class
func baseSubscriber<S: Subscribable, T>(_ subscribable: S) -> (RxSwift.Event<T>) -> Void where S.DataType == T {
    return { [weak subscribable] event in
        switch event {
        case let .next(element):
            subscribable?.onNext(element)
        case .completed:
           subscribable.onCompleted()
        case let .error(error):
            subscribable.onError(error)
        }
    }
}

You can call the new function like this:

baseSubscriber(someMvpView)
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • Thanks, ill try this when i got on my station, one question, can I override the methods, or create like an abstract method on java, so that I can still make specific actions on each event? – Tenten Ponce Mar 01 '18 at 05:40
  • 1
    @TentenPonce you're trying too hard to use classes. Understandable given your Android background :). If you need custom error actions for each caller, you could add a protocol with methods for handling the errors and conform to that protocol for every class whose instance you pass to the function. – Cristik Mar 01 '18 at 05:44
  • I guess I should read more for this. I just can't comprehend on how to make it work, (implementing it inline? xD), on my current problem, its just different from my java world hahaha. By the way, thank you so much, it solves my question :) – Tenten Ponce Mar 01 '18 at 06:21
  • @TentenPonce I can add some sample code for this, but need to better undestand your context - e.g. why do you need references to both view and presenter? Who's presenting the error (I assume the presenter). – Cristik Mar 01 '18 at 07:59
  • Hi @Cristik, as of now, I've managed to do it, I just don't know if it is the right way. I'm passing a `closure` as parameter, `onNext`, `onError` and `onCompleted` parameters. Thinking of posting it on a stack exchange code review to know if it is right xD I've also not yet trying to pass the `mvpView` because in swift, it is the protocol. On java, I'm passing the `basePresenter` just to check if it is attached, then I'll call `mvpView.showGenericErrorDialog()` from the passed `mvpView`. `mvpView` is an interface on my java. – Tenten Ponce Mar 01 '18 at 08:04
  • If you can provide a sample code then it will be very helpful to me, so I can compare on what did I do. I'm just shy on asking for code, I just don't like the feeling of spoon feed lol. hahaha. Sorry and thank you :) – Tenten Ponce Mar 01 '18 at 08:07
  • @TentenPonce sure, I can provide some code, however I don't fully understand how do you intended to use the class-based approach. Maybe add some pseudo-code in the question related to this particular error handling specific to every handler? – Cristik Mar 01 '18 at 08:11
  • Hi @Cristik, sorry for the late response, stackoverflow didn't notify me haha. I've updated my question with my latest code, how can I override methods (like the onError) so if in case I want to disregard it? – Tenten Ponce Mar 05 '18 at 03:07
  • @TentenPonce I added an update to my answer trying to explain how you can solve the new problem without using classes. – Cristik Mar 06 '18 at 11:10
  • Hi @Cristik, there's an error on `extension: BaseMvpView: Subscribable`, it tells that: Extension of protocol 'BaseMvpView' cannot have an inheritance clause, by the way, thanks for the effort, very appreciate it :) – Tenten Ponce Mar 06 '18 at 11:25
  • @TentenPonce Is `BaseMvpView` already a protocol? I thought it's a class. In this case you need to put the `Subscribable` conformance on the classes the conform to `BaseMvpView` – Cristik Mar 06 '18 at 11:26
  • Yes, it is a protocol, sorry I forgot to include it on my updated snippet. – Tenten Ponce Mar 06 '18 at 11:27
  • Also sir, the parameter `subscribable` has an error because it has typealias (i think), it tells that: Associated type 'DataType' can only be used with a concrete type or generic parameter. – Tenten Ponce Mar 06 '18 at 11:31
  • @TentenPonce oh yeah, updated the signature of `baseSubscriber`. That's what happens when you write code straight in the SO editor :P – Cristik Mar 06 '18 at 11:34
  • Trying to implement it, can you also add how to add the parameter `subscribable`? or how to use it, sorry for being very dumb here, Im trying my best, im very sorry. – Tenten Ponce Mar 06 '18 at 11:45
  • @TentenPonce calling is very simple, added a sample in the updated answer. – Cristik Mar 06 '18 at 11:49
  • Trying to puzzle it out, please see also my updated question by the way. – Tenten Ponce Mar 06 '18 at 11:52
  • @TentenPonce this is turning into an ongoing question, which is not really fit for SO... – Cristik Mar 06 '18 at 11:55
  • I understand, sorry, if I can just upvote your answer multiple times for your extra mile of help :) thanks again sir, I'll try to solve this and read more. Thank you! – Tenten Ponce Mar 06 '18 at 12:01
  • 1
    @TentenPonce it's likely that you'll find answers to the new problems you ran into here on SO or other sites. And if you can't find the answers, or can't figure out how to do a certain thing, you can always ask a new question. – Cristik Mar 06 '18 at 13:14