Vinoth Kumar
Vinoth Kumar

Reputation: 57

RxSwift Subscriber receiving multiple events

Consider the below code.

  1. On tapButton, we subscribe to an Observable isFetched and then call fetchPopularMovies().
  2. fetchPopularMovies() in turn calls an API. When the response is received, we will send OnNext(true) event.

Problem is, I receive multiple events on 2nd button tap onwards. If I add onCompleted(), I don't even receive events on 2nd button tap onwards. My expectation is that one event will be triggered on each button tap. What am I missing here?

class ViewController: UIViewController {

    let popularMoviesURL = URL(string: "https://api.themoviedb.org/3/movie/popular?api_key=API_KEY")
    var isFetched = BehaviorSubject<Bool?>(value:nil)
    let disposeBag = DisposeBag()
        
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapButton(_ sender: Any) {
        let observable = isFetched.asObservable()
        observable.subscribe(onNext: { observer in
            guard let result = observer else { return }
            print(result)
            print("onNext Recieved")
            
        }, onError: { _ in
            print("onError Recieved")
        }).disposed(by: disposeBag)
        
        fetchPopularMovies()
    }
    
    func fetchPopularMovies() {
        let task = URLSession.shared.dataTask(with: popularMoviesURL!) {(data, response, error) in
            guard let _ = data else { return }
            self.isFetched.onNext(true)
           //self.isFetched.onCompleted()
        }
        task.resume()
    }
}

Upvotes: 1

Views: 1483

Answers (1)

Daniel T.
Daniel T.

Reputation: 33979

Reactive code is declarative. It's "setup" code. So it should be placed where the comment says "Do any additional setup after loading the view."

The simplest change you can do to fix the problem you are having is to move the subscription into the viewDidLoad method as Satish Patel referenced in his comment.

override func viewDidLoad() {
    super.viewDidLoad()
    isFetched.subscribe(onNext: { observer in
        guard let result = observer else { return }
        print(result)
        print("onNext Recieved")

    }, onError: { _ in
        print("onError Recieved")
    }).disposed(by: disposeBag)
}

@IBAction func tapButton(_ sender: Any) {
    fetchPopularMovies()
}

(Note that Subjects should always be held with lets never vars.)

If you use RxCocoa, you can simplify this code even more:

class ViewController: UIViewController {
    let button = UIButton()
    let isFetched = BehaviorSubject<Bool?>(value:nil)
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        let popularMoviesURL = URL(string: "https://api.themoviedb.org/3/movie/popular?api_key=API_KEY")!

        let fetchedData = button.rx.tap
            .flatMapLatest {
                URLSession.shared.rx.data(request: URLRequest(url: popularMoviesURL))
                    .catch { error in
                        print("onError Recieved")
                        return Observable.empty()
                    }
            }

        fetchedData
            .map { _ in true }
            .bind(to: isFetched)
            .disposed(by: disposeBag)
    }
}

Now, all of your code is "setup code" so it all goes in viewDidLoad.

Upvotes: 0

Related Questions