diff --git a/Podfile b/Podfile
index 8c58b81..2c060ee 100644
--- a/Podfile
+++ b/Podfile
@@ -5,4 +5,7 @@ target 'RxTutorials' do
pod 'RxCocoa'
pod 'RxDataSources'
pod 'ChameleonFramework/Swift'
+ pod 'ObjectMapper'
+ pod 'RxAlamofire'
+ pod 'AlamofireNetworkActivityIndicator'
end
\ No newline at end of file
diff --git a/Podfile.lock b/Podfile.lock
index 4cf2b7b..a6036f5 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -1,7 +1,16 @@
PODS:
+ - Alamofire (3.4.1)
+ - AlamofireNetworkActivityIndicator (1.0.1):
+ - Alamofire (~> 3.3)
- ChameleonFramework/Default (2.1.0)
- ChameleonFramework/Swift (2.1.0):
- ChameleonFramework/Default
+ - ObjectMapper (1.3.0)
+ - RxAlamofire (2.5):
+ - RxAlamofire/Core (= 2.5)
+ - RxAlamofire/Core (2.5):
+ - Alamofire (~> 3.4)
+ - RxSwift (~> 2.5)
- RxCocoa (2.6.0):
- RxSwift (~> 2.5)
- RxDataSources (0.9):
@@ -10,17 +19,24 @@ PODS:
- RxSwift (2.6.0)
DEPENDENCIES:
+ - AlamofireNetworkActivityIndicator
- ChameleonFramework/Swift
+ - ObjectMapper
+ - RxAlamofire
- RxCocoa
- RxDataSources
- RxSwift
SPEC CHECKSUMS:
+ Alamofire: 01a82e2f6c0f860ade35534c8dd88be61bdef40c
+ AlamofireNetworkActivityIndicator: 49a6e39afb947136f7b1872bae2d684957c26697
ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64
+ ObjectMapper: 4eb08a726e9791a93b2632049951b56a7c17e471
+ RxAlamofire: b4572a077b6b2410c8732a27266299df4c00736e
RxCocoa: 89ab00d5753502520227940e3a0e0a0413683c6b
RxDataSources: e169bba94a7be584df7c1e1487fa56edc8753ae6
RxSwift: 77f3a0b15324baa7a1c9bfa9f199648a82424e26
-PODFILE CHECKSUM: eddc4457677bde2bc3f6299c3462d1de50e45772
+PODFILE CHECKSUM: acdb8030ddfcf2a95ba2376b3f556d7e1aa82091
COCOAPODS: 1.0.0
diff --git a/RxTutorials.xcodeproj/project.pbxproj b/RxTutorials.xcodeproj/project.pbxproj
index 33304b7..6f6a4af 100644
--- a/RxTutorials.xcodeproj/project.pbxproj
+++ b/RxTutorials.xcodeproj/project.pbxproj
@@ -43,6 +43,281 @@
isa
PBXBuildFile
+ 0D7041851D58D0D80061DDA0
+
+ children
+
+ 0D7041861D58D1F80061DDA0
+
+ isa
+ PBXGroup
+ path
+ Models
+ sourceTree
+ <group>
+
+ 0D7041861D58D1F80061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ Repository.swift
+ sourceTree
+ <group>
+
+ 0D7041871D58D1F80061DDA0
+
+ fileRef
+ 0D7041861D58D1F80061DDA0
+ isa
+ PBXBuildFile
+
+ 0D7041881D58D4890061DDA0
+
+ children
+
+ 0D7041891D58D4B20061DDA0
+
+ isa
+ PBXGroup
+ path
+ Networking Search bar
+ sourceTree
+ <group>
+
+ 0D7041891D58D4B20061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ NetworkingSearchBarViewController.swift
+ sourceTree
+ <group>
+
+ 0D70418A1D58D4B20061DDA0
+
+ fileRef
+ 0D7041891D58D4B20061DDA0
+ isa
+ PBXBuildFile
+
+ 0D70418B1D58D6350061DDA0
+
+ children
+
+ 0D70418F1D58D9940061DDA0
+ 0D70418C1D58D6530061DDA0
+
+ isa
+ PBXGroup
+ path
+ Services
+ sourceTree
+ <group>
+
+ 0D70418C1D58D6530061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ RepositoriesNetworkService.swift
+ sourceTree
+ <group>
+
+ 0D70418D1D58D6530061DDA0
+
+ fileRef
+ 0D70418C1D58D6530061DDA0
+ isa
+ PBXBuildFile
+
+ 0D70418E1D58D9800061DDA0
+
+ children
+
+ 0D7041971D58E2F40061DDA0
+
+ isa
+ PBXGroup
+ path
+ API
+ sourceTree
+ <group>
+
+ 0D70418F1D58D9940061DDA0
+
+ children
+
+ 0D7041901D58D9A60061DDA0
+
+ isa
+ PBXGroup
+ path
+ Networking
+ sourceTree
+ <group>
+
+ 0D7041901D58D9A60061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ Router.swift
+ sourceTree
+ <group>
+
+ 0D7041911D58D9A60061DDA0
+
+ fileRef
+ 0D7041901D58D9A60061DDA0
+ isa
+ PBXBuildFile
+
+ 0D7041921D58DB250061DDA0
+
+ children
+
+ 0D7041931D58DB4B0061DDA0
+ 0D7041951D58E14B0061DDA0
+
+ isa
+ PBXGroup
+ path
+ Error
+ sourceTree
+ <group>
+
+ 0D7041931D58DB4B0061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ Error.swift
+ sourceTree
+ <group>
+
+ 0D7041941D58DB4B0061DDA0
+
+ fileRef
+ 0D7041931D58DB4B0061DDA0
+ isa
+ PBXBuildFile
+
+ 0D7041951D58E14B0061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ LocalizableString.swift
+ sourceTree
+ <group>
+
+ 0D7041961D58E14B0061DDA0
+
+ fileRef
+ 0D7041951D58E14B0061DDA0
+ isa
+ PBXBuildFile
+
+ 0D7041971D58E2F40061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ RepositoriesAPI.swift
+ sourceTree
+ <group>
+
+ 0D7041981D58E2F40061DDA0
+
+ fileRef
+ 0D7041971D58E2F40061DDA0
+ isa
+ PBXBuildFile
+
+ 0D7041991D58F0980061DDA0
+
+ children
+
+ 0D70419A1D58F0C40061DDA0
+
+ isa
+ PBXGroup
+ name
+ Elegant Networking Search bar
+ path
+ Elegant Networking Search bar
+ sourceTree
+ <group>
+
+ 0D70419A1D58F0C40061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ ElegantNetworkingSearchBarViewController.swift
+ sourceTree
+ <group>
+
+ 0D70419B1D58F0C40061DDA0
+
+ fileRef
+ 0D70419A1D58F0C40061DDA0
+ isa
+ PBXBuildFile
+
+ 0D70419C1D58F4790061DDA0
+
+ fileEncoding
+ 4
+ isa
+ PBXFileReference
+ lastKnownFileType
+ sourcecode.swift
+ path
+ RepositoriesViewModel.swift
+ sourceTree
+ <group>
+
+ 0D70419D1D58F4790061DDA0
+
+ fileRef
+ 0D70419C1D58F4790061DDA0
+ isa
+ PBXBuildFile
+
0DBAFB001D53858900D49D70
fileEncoding
@@ -121,6 +396,7 @@
children
+ 0D70419C1D58F4790061DDA0
0DBAFB081D539F3F00D49D70
isa
@@ -292,8 +568,6 @@
isa
PBXGroup
- name
- Basic Search bar
path
Basic Search bar
sourceTree
@@ -402,18 +676,27 @@
2147483647
files
+ 0D70418A1D58D4B20061DDA0
0DBAFB0D1D53A61900D49D70
0DBAFB1B1D53E27E00D49D70
0DCFC4E81D5148B9001FD547
+ 0D7041871D58D1F80061DDA0
0DBAFB141D53C79300D49D70
+ 0D70418D1D58D6530061DDA0
0DBAFB091D539F3F00D49D70
+ 0D7041911D58D9A60061DDA0
+ 0D7041941D58DB4B0061DDA0
+ 0D70419D1D58F4790061DDA0
0DBAFB171D53D11C00D49D70
0DBAFB061D539E4400D49D70
0D1675201D52509C0072EC03
+ 0D7041961D58E14B0061DDA0
0DBAFB0C1D53A61900D49D70
+ 0D7041981D58E2F40061DDA0
0DCFC4D21D5144E9001FD547
0DBAFB021D53858900D49D70
0DBAFB031D53858900D49D70
+ 0D70419B1D58F0C40061DDA0
isa
PBXSourcesBuildPhase
@@ -506,8 +789,12 @@
children
+ 0D70418E1D58D9800061DDA0
0D16751E1D52491F0072EC03
0DCFC4E61D514858001FD547
+ 0D7041921D58DB250061DDA0
+ 0D7041851D58D0D80061DDA0
+ 0D70418B1D58D6350061DDA0
0DCFC4E51D514853001FD547
0DBAFB071D539ED700D49D70
0DCFC4D11D5144E9001FD547
@@ -820,6 +1107,8 @@
AppIcon
INFOPLIST_FILE
RxTutorials/Info.plist
+ IPHONEOS_DEPLOYMENT_TARGET
+ 9.3
LD_RUNPATH_SEARCH_PATHS
$(inherited) @executable_path/Frameworks
PRODUCT_BUNDLE_IDENTIFIER
@@ -842,6 +1131,8 @@
AppIcon
INFOPLIST_FILE
RxTutorials/Info.plist
+ IPHONEOS_DEPLOYMENT_TARGET
+ 9.3
LD_RUNPATH_SEARCH_PATHS
$(inherited) @executable_path/Frameworks
PRODUCT_BUNDLE_IDENTIFIER
@@ -876,6 +1167,8 @@
0DBAFB151D53D0EC00D49D70
0DBAFB0F1D53ADCA00D49D70
0DBAFB191D53E25600D49D70
+ 0D7041991D58F0980061DDA0
+ 0D7041881D58D4890061DDA0
0DBAFB101D53C70900D49D70
0DBAFB0E1D53ADBD00D49D70
0DCFC4E71D5148B9001FD547
diff --git a/RxTutorials/API/RepositoriesAPI.swift b/RxTutorials/API/RepositoriesAPI.swift
new file mode 100644
index 0000000..eb4e46f
--- /dev/null
+++ b/RxTutorials/API/RepositoriesAPI.swift
@@ -0,0 +1,23 @@
+//
+// RepositoriesAPI.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+import RxSwift
+import Alamofire
+
+struct RepositoriesAPI {
+
+ private static let disposeBag = DisposeBag()
+
+ static func searchRepositories(query query: String) -> Observable<[Repository]> {
+
+ return RepositoriesNetworkService.searchRepositories(query: query)
+ }
+
+
+}
\ No newline at end of file
diff --git a/RxTutorials/AppDelegate.swift b/RxTutorials/AppDelegate.swift
index 45947d8..21ab3c0 100644
--- a/RxTutorials/AppDelegate.swift
+++ b/RxTutorials/AppDelegate.swift
@@ -8,6 +8,7 @@
import UIKit
import ChameleonFramework
+import AlamofireNetworkActivityIndicator
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -18,6 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
customAppAppereance()
+ NetworkActivityIndicatorManager.sharedManager.isEnabled = true
return true
}
diff --git a/RxTutorials/Controllers/Elegant Networking Search bar/ElegantNetworkingSearchBarViewController.swift b/RxTutorials/Controllers/Elegant Networking Search bar/ElegantNetworkingSearchBarViewController.swift
new file mode 100644
index 0000000..37d3b17
--- /dev/null
+++ b/RxTutorials/Controllers/Elegant Networking Search bar/ElegantNetworkingSearchBarViewController.swift
@@ -0,0 +1,67 @@
+//
+// ElegantNetworkingSearchBarViewController.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+import RxDataSources
+
+class ElegantNetworkingSearchBarViewController: UIViewController {
+
+ // MARK: - IBOutlets
+ @IBOutlet weak private var tableView:UITableView!
+ @IBOutlet weak private var searchBar:UISearchBar!
+
+ // MARK: - Properties
+ let disposeBag = DisposeBag()
+ var viewModel:RepositoriesViewModel = RepositoriesViewModel()
+
+ // MARK: - Lifecycle
+
+ // MARK: - View Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setUpView()
+ }
+
+ // MARK: - Internal Helpers
+
+ private func setUpView(){
+ title = "Elegant GitHub Repos"
+ setUpNavigationBar()
+ setUpSearchBar()
+ }
+
+ private func setUpNavigationBar(){
+
+ }
+
+ private func setUpSearchBar(){
+
+ searchBar.placeholder = "Enter github username"
+
+ searchBar.rx_text
+ .filter{$0.characters.count > 0 }
+ .bindTo(viewModel.searchText)
+ .addDisposableTo(disposeBag)
+
+ searchBar.rx_cancelButtonClicked
+ .map{""}
+ .bindTo(viewModel.searchText)
+ .addDisposableTo(disposeBag)
+
+
+ viewModel.data
+ .drive(tableView.rx_itemsWithCellIdentifier("Cell")){ _, repository, cell in
+ cell.textLabel?.text = repository.name
+ cell.detailTextLabel?.text = repository.url
+ }.addDisposableTo(disposeBag)
+ }
+
+}
diff --git a/RxTutorials/Controllers/Networking Search bar/NetworkingSearchBarViewController.swift b/RxTutorials/Controllers/Networking Search bar/NetworkingSearchBarViewController.swift
new file mode 100644
index 0000000..9a61ed6
--- /dev/null
+++ b/RxTutorials/Controllers/Networking Search bar/NetworkingSearchBarViewController.swift
@@ -0,0 +1,84 @@
+//
+// NetworkingSearchBarViewController.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import UIKit
+import RxSwift
+import RxCocoa
+
+class NetworkingSearchBarViewController: UIViewController {
+
+ // MARK: - IBOutlets
+ @IBOutlet weak private var tableView:UITableView!
+ @IBOutlet weak private var searchBar:UISearchBar!
+
+ // MARK: - Properties
+ let disposeBag = DisposeBag()
+ var repositoryNetworkModel: RepositoriesNetworkService!
+
+
+ var searchText: Observable{
+ return searchBar.rx_text
+ .filter {$0.characters.count > 0}
+ .throttle(0.3, scheduler: MainScheduler.instance)
+ .distinctUntilChanged()
+ }
+
+ // MARK: - View Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setUpView()
+ }
+
+ // MARK: - Internal Helpers
+
+ private func setUpView(){
+ title = "GitHub Repos"
+ setUpNavigationBar()
+ setUpSearchBar()
+ setupRx()
+ }
+
+ private func setUpNavigationBar(){
+
+ tableView.tableFooterView = UIView()
+ }
+
+ private func setUpSearchBar(){
+
+ searchBar.placeholder = "Enter github username"
+
+ }
+
+ func setupRx() {
+
+ repositoryNetworkModel = RepositoriesNetworkService(withNameObservable: searchText)
+
+ repositoryNetworkModel
+ .rx_repositories
+ .drive(tableView.rx_itemsWithCellFactory) { (tv, i, repository) in
+ let cell = tv.dequeueReusableCellWithIdentifier("Cell", forIndexPath: NSIndexPath(forRow: i, inSection: 0))
+ cell.textLabel?.text = repository.name
+
+ return cell
+ }
+ .addDisposableTo(disposeBag)
+
+ repositoryNetworkModel
+ .rx_repositories
+ .driveNext { repositories in
+ if repositories.count == 0 {
+ print("No repositories")
+ }
+ }
+ .addDisposableTo(disposeBag)
+ }
+
+
+
+}
diff --git a/RxTutorials/Controllers/TutorialsListViewController.swift b/RxTutorials/Controllers/TutorialsListViewController.swift
index fe99e4e..bd21311 100644
--- a/RxTutorials/Controllers/TutorialsListViewController.swift
+++ b/RxTutorials/Controllers/TutorialsListViewController.swift
@@ -32,8 +32,10 @@ class TutorialsListViewController: UIViewController {
case SectionedTableViewReload = "SectionedTableViewReload"
case SectionedTableViewAnimated = "SectionedTableViewAnimated"
case BasicSearchBar = "BasicSearchBarViewController"
+ case NetworkingSearchBar = "NetworkingSearchBarViewController"
+ case ElegantNetworkingSearchBar = "ElegantNetworkingSearchBarViewController"
- static let values :[DataSource] = [.BasicControls,.TwoWayBinding,.SectionedTableViewReload,.SectionedTableViewAnimated,.BasicSearchBar]
+ static let values :[DataSource] = [.BasicControls,.TwoWayBinding,.SectionedTableViewReload,.SectionedTableViewAnimated,.BasicSearchBar,.NetworkingSearchBar,.ElegantNetworkingSearchBar]
}
enum CellIdentifier:String {
diff --git a/RxTutorials/Error/Error.swift b/RxTutorials/Error/Error.swift
new file mode 100644
index 0000000..9538af1
--- /dev/null
+++ b/RxTutorials/Error/Error.swift
@@ -0,0 +1,93 @@
+//
+// ApiError.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+import Alamofire
+
+protocol ErrorPresentable: ErrorType {
+ var title: String? { get }
+ var message: String? { get }
+}
+
+struct ApiError : ErrorPresentable {
+
+ // MARK: - Properties
+ var title: String?
+ var message: String?
+ var code: ApiErrorCode?
+
+ // MARK: - Initializers
+ init(title: String? = nil, message: String? = nil, code: ApiErrorCode? = nil) {
+ self.title = title
+ self.message = message
+ self.code = code
+ }
+
+ init?(error: NSError, data: NSData?) {
+
+ if Int(CFNetworkErrors.CFURLErrorNotConnectedToInternet.rawValue) == error.code {
+ title = LocalizableString.offlineError.localizedString
+ message = LocalizableString.checkInternet.localizedString
+ } else if let errorMessage = data?.apiErrorMessage {
+ message = errorMessage
+ if let code = data?.apiErrorCode, apiErrorCode = ApiErrorCode(rawValue: code) {
+ self.code = apiErrorCode
+ }
+ } else {
+ return nil
+ }
+ }
+
+ // MARK: - Utils
+ static var defaultError: ApiError {
+ return ApiError(
+ title: LocalizableString.connectionError.localizedString,
+ message: LocalizableString.connectionProblem.localizedString,
+ code: nil)
+ }
+}
+
+enum ApiErrorCode: Int {
+ // TODO: Set error Codes
+ case ErrorUnknown = 0
+ case sampleCase = 1
+}
+
+extension NSData {
+ func toJSONDictionary() -> [String: AnyObject]? {
+
+ guard let json = try? NSJSONSerialization.JSONObjectWithData(self, options: []) else {
+ return nil
+ }
+ guard let jsonDic = json as? [String: AnyObject] else {
+ return nil
+ }
+ return jsonDic
+ }
+
+ var apiErrorMessage: String? {
+ if let jsonDic = toJSONDictionary(),
+ let errorMessage = jsonDic["message"] as? String {
+ return errorMessage
+ }
+
+ return nil
+ }
+
+ var apiErrorCode: Int? {
+ if let jsonDic = toJSONDictionary(),
+ let errorCode = jsonDic["code"] as? Int {
+ return errorCode
+ }
+
+ return nil
+ }
+}
+
+
+
diff --git a/RxTutorials/Error/LocalizableString.swift b/RxTutorials/Error/LocalizableString.swift
new file mode 100644
index 0000000..5c41340
--- /dev/null
+++ b/RxTutorials/Error/LocalizableString.swift
@@ -0,0 +1,22 @@
+//
+// LocalizableString.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+enum LocalizableString: String {
+
+ var localizedString: String {
+ return NSLocalizedString(rawValue, comment: "")
+ }
+
+ //MARK: - Global
+ case connectionError = "Connection error"
+ case connectionProblem = "There was a problem, please try again."
+ case offlineError = "Offline error"
+ case checkInternet = "Check your internet connection and try again."
+
+}
\ No newline at end of file
diff --git a/RxTutorials/Models/Repository.swift b/RxTutorials/Models/Repository.swift
new file mode 100644
index 0000000..3bbdd74
--- /dev/null
+++ b/RxTutorials/Models/Repository.swift
@@ -0,0 +1,26 @@
+//
+// Repository.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+import ObjectMapper
+
+class Repository: Mappable {
+ var identifier: Int!
+ var language: String!
+ var url: String!
+ var name: String!
+
+ required init?(_ map: Map) { }
+
+ func mapping(map: Map) {
+ identifier <- map["id"]
+ language <- map["language"]
+ url <- map["url"]
+ name <- map["name"]
+ }
+}
diff --git a/RxTutorials/Services/Networking/Router.swift b/RxTutorials/Services/Networking/Router.swift
new file mode 100644
index 0000000..92d213c
--- /dev/null
+++ b/RxTutorials/Services/Networking/Router.swift
@@ -0,0 +1,70 @@
+//
+// Router.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+import Alamofire
+
+typealias JSONDictionary = [String: AnyObject]
+
+
+enum Router: URLRequestConvertible {
+
+ private struct Request {
+ let method: Alamofire.Method
+ let path: String
+ let encoding: ParameterEncoding?
+ let parameters: JSONDictionary?
+
+ init(method: Alamofire.Method,
+ path: String,
+ encoding: ParameterEncoding? = nil,
+ parameters: JSONDictionary? = nil){
+
+ self.method = method
+ self.path = path
+ self.encoding = encoding
+ self.parameters = parameters
+ }
+ }
+
+ static let baseHostPath = "https://api.github.com/"
+
+ var baseURLPath: String {
+
+ switch self {
+ default:
+ return "\(Router.baseHostPath)"
+ }
+ }
+
+ case Repositories(String)
+
+ private var request: Request {
+ switch self {
+ //Repositories
+ case .Repositories(let query):
+ return Request(method: .GET,
+ path: "users/\(query)/repos",
+ encoding: .JSON)
+ }
+ }
+
+ var URLRequest: NSMutableURLRequest {
+
+ let URL = NSURL(string: baseURLPath)!
+ let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(request.path))
+ mutableURLRequest.HTTPMethod = request.method.rawValue
+ mutableURLRequest.timeoutInterval = NSTimeInterval(10 * 1000)
+
+ if let encoding = request.encoding {
+ return encoding.encode(mutableURLRequest, parameters: request.parameters).0
+ } else {
+ return mutableURLRequest
+ }
+ }
+}
\ No newline at end of file
diff --git a/RxTutorials/Services/RepositoriesNetworkService.swift b/RxTutorials/Services/RepositoriesNetworkService.swift
new file mode 100644
index 0000000..734676f
--- /dev/null
+++ b/RxTutorials/Services/RepositoriesNetworkService.swift
@@ -0,0 +1,81 @@
+//
+// RepositoriesNetworkService.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+import RxSwift
+import RxCocoa
+import ObjectMapper
+import RxAlamofire
+import Alamofire
+
+struct RepositoriesNetworkService {
+
+ // driver way
+ lazy var rx_repositories: Driver<[Repository]> = self.fetchRepositories()
+ private var repositoryName: Observable
+
+ init(withNameObservable nameObservable: Observable) {
+ self.repositoryName = nameObservable
+ }
+
+ private func fetchRepositories() -> Driver<[Repository]> {
+ return repositoryName
+ .subscribeOn(MainScheduler.instance) // Make sure we are on MainScheduler
+ .doOn(onNext: { response in
+ UIApplication.sharedApplication().networkActivityIndicatorVisible = true
+ })
+ .observeOn(ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .Background))
+ .flatMapLatest { text in // .Background thread, network request
+ return RxAlamofire
+ .requestJSON(.GET, "https://api.github.com/users/\(text)/repos")
+ .catchError { error in
+ return Observable.never()
+ }
+ }
+ .observeOn(ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .Background))
+ .map { (response, json) -> [Repository] in // again back to .Background, map objects
+ if let repos = Mapper().mapArray(json) {
+ return repos
+ } else {
+ return []
+ }
+ }
+ .observeOn(MainScheduler.instance) // switch to MainScheduler, UI updates
+ .doOn(onNext: { response in
+ UIApplication.sharedApplication().networkActivityIndicatorVisible = false
+ })
+ .asDriver(onErrorJustReturn: []) // This also makes sure that we are on MainScheduler
+ }
+
+
+ // Elegant way
+ static func searchRepositories(query query:String) -> Observable<[Repository]>{
+
+ return Observable.create{ (observer) -> Disposable in
+
+ Alamofire.request(Router.Repositories(query))
+ .validate()
+ .responseJSON { (response) in
+ switch response.result {
+ case .Success(let jsonData):
+
+ guard let repos: [Repository] = Mapper().mapArray(jsonData) else {
+ observer.onError(ApiError.defaultError)
+ break
+ }
+ observer.onNext(repos)
+ observer.onCompleted()
+ case .Failure(let error):
+ let apiError = ApiError(error: error, data: response.data) ?? .defaultError
+ observer.onError(apiError)
+ }
+ }
+ return NopDisposable.instance
+ }
+ }
+}
\ No newline at end of file
diff --git a/RxTutorials/View/Base.lproj/Main.storyboard b/RxTutorials/View/Base.lproj/Main.storyboard
index 541d141..54cb32c 100644
--- a/RxTutorials/View/Base.lproj/Main.storyboard
+++ b/RxTutorials/View/Base.lproj/Main.storyboard
@@ -571,6 +571,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -744,5 +803,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RxTutorials/ViewModel/RepositoriesViewModel.swift b/RxTutorials/ViewModel/RepositoriesViewModel.swift
new file mode 100644
index 0000000..d4905e2
--- /dev/null
+++ b/RxTutorials/ViewModel/RepositoriesViewModel.swift
@@ -0,0 +1,28 @@
+
+//
+// RepositoriesViewModel.swift
+// RxTutorials
+//
+// Created by Abel Osorio on 8/8/16.
+// Copyright © 2016 Abel Osorio. All rights reserved.
+//
+
+import Foundation
+import RxSwift
+import RxCocoa
+
+struct RepositoriesViewModel {
+
+ let searchText = Variable("")
+ let disposeBag = DisposeBag()
+
+ lazy var data:Driver<[Repository]> = {
+ return self.searchText.asObservable()
+ .filter{$0.characters.count > 0}
+ .throttle(0.3,scheduler:MainScheduler.instance)
+ .flatMapLatest{
+ RepositoriesAPI.searchRepositories(query: $0).catchErrorJustReturn([])
+ }.asDriver(onErrorJustReturn:[])
+ }()
+
+}
\ No newline at end of file