如何在运行时从活动或片段向dagger模块传递参数

时间:2018-12-28 18:20:41

标签: android dagger-2 dagger

我的软件规格如下:

Android Studio 3.4
dagger-android 2.16

我有一个通过MapboxGeocoder的以下类,该类将执行并返回响应。

class GeocodingImp(private val mapboxGeocoder: MapboxGeocoder) : Geocoding {

    override fun getCoordinates(address: String, criteria: String): AddressCoordinate {
        val response = mapboxGeocoder.execute()

        return if(response.isSuccess && !response.body().features.isEmpty()) {
            AddressCoordinate(
                response.body().features[0].latitude,
                response.body().features[0].longitude)
        }
        else {
            AddressCoordinate(0.0, 0.0)
        }
    }
}

但是,MapboxGeocoder是在编译时在Dagger模块中生成的。因此,我必须为地址和TYPE_ADDRESS指定字符串。

@Reusable
@Named("address")
@Provides
fun provideAddress(): String = "the address to get coordinates from"

@Reusable
@Provides
@Named("geocoder_criteria")
fun provideGeocoderCriteria(): String = GeocoderCriteria.TYPE_ADDRESS

@Reusable
@Provides
fun provideMapboxGeocoder(@Named("address") address: String, @Named("geocoder_criteria") geocoderCriteria: String): MapboxGeocoder =
    MapboxGeocoder.Builder()
        .setAccessToken("api token")
        .setLocation(address)
        .setType(geocoderCriteria)
        .build()

@Reusable
@Provides
fun provideGeocoding(mapboxGeocoder: MapboxGeocoder): Geocoding =
    GeocodingImp(mapboxGeocoder)

我的component班:

interface TMDispatchMobileUIComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: TMDispatchMobileUIApplication): Builder

        fun build(): TMDispatchMobileUIComponent
    }

    fun inject(application: TMDispatchMobileUIApplication)
}

在主要活动中,我将这样使用,因为用户可以输入其他地址或将条件更改为其他内容。但是在编译模块时,我无法在运行时将任何参数传递给它们:

presenter.getAddressCoordinates("this should be the actual address", GeocoderCriteria.TYPE_ADDRESS)

对于我注入到活动中,我使用以下内容:

AndroidInjection.inject(this)

这个问题有解决方案吗?

4 个答案:

答案 0 :(得分:6)

您可以使用“辅助注射”方法解决您的问题。

这意味着您既需要使用现有范围提供的依赖关系,又需要使用实例创建者的某些依赖关系(在本例中为您的主要活动)来构建类。 Google的Guice有一个不错的description of what it is and why it is needed

不幸的是,Dagger 2没有开箱即用的功能。但是,杰克·沃顿(Jake Wharton)正在研究可以附加到Dagger的separate library。此外,您可以在他关于Droidcon London 2018的演讲中找到更多详细信息,在该演讲中他专门讨论了这个问题: https://jakewharton.com/helping-dagger-help-you/

答案 1 :(得分:3)

与已经给出的答案相比,另一种方法是通过匕首依赖注入(称为GeoModelFactory)获得“工厂”,该工厂可以为您创建GeoModel的新实例。

您可以将地址传递给创建实例的工厂。为了进行优化,您可以存储已请求的所有不同地址/类型的引用(如果不删除旧地址/类型,则如果有很多不同的地址/类型,可能会导致内存泄漏),或者如果仅存储,也可能就足够了最新实例以及代码的其他部分,只是要求工厂为您提供最后创建的GeoModel。

答案 2 :(得分:1)

MapboxGeocoder是在运行时动态构造的,在这种情况下,匕首的作用不大,因为它的目的是像在编写代码时那样,帮助您在编译时构造对象图。

因此,我认为您应该在MapboxGeocoder内创建一个getCoordinates()

答案 3 :(得分:1)

如果愿意,可以在运行时重新创建整个组件,然后在该处将参数作为构造函数参数传递给模块。像这样:

import UIKit
import Foundation
import Alamofire
import SwiftyJSON

class ScheduleTableViewController: UITableViewController {

    var schedule: [DayItem]
    var displayDay: Int
    var numDays: Int?
    @IBOutlet weak var prevDayButton: UIBarButtonItem!
    @IBOutlet weak var nextDayButton: UIBarButtonItem!


    override func viewDidLoad() {
        super.viewDidLoad()
        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
        //schedule = scrapeSchedule()
//        let add = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: nil)//action: #selector(addTapped))
//        navigationController!.toolbarItems = [add]
        let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
        rightSwipe.direction = .right
        view.addGestureRecognizer(rightSwipe)

        let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
        leftSwipe.direction = .left
        view.addGestureRecognizer(leftSwipe)
    }

    required init?(coder aDecoder: NSCoder) {
        self.displayDay = 0
        self.schedule = [DayItem]()
        self.numDays = 0
        super.init(coder: aDecoder)
        refresh(self)
    }

    @objc func handleSwipes(_ sender: UISwipeGestureRecognizer) {
        if sender.direction == .right {
            if prevDayButton.isEnabled {
                prevDay(self)
            } else {
                dismiss(animated: true, completion: nil)
            }
        } else if sender.direction == .left {
            if nextDayButton.isEnabled {
                nextDay(self)
            }
        }
    }

    func reloadData() {
        tableView.reloadData()
        if schedule.count != 0 {
            self.navigationItem.title = self.schedule[self.displayDay].day
        }
    }

    func configureDayButtons() {
        if schedule.count == 0 {
            prevDayButton.isEnabled = false
            nextDayButton.isEnabled = false
        } else {
            prevDayButton.isEnabled = true
            nextDayButton.isEnabled = true

            if displayDay == 0 {
                prevDayButton.isEnabled = false
            }

            if displayDay == numDays! - 1 {
                nextDayButton.isEnabled = false
            }
        }
    }

    @IBAction func prevDay(_ sender: Any) {
        if displayDay != 0 {
            displayDay -= 1
            configureDayButtons()
            reloadData()
        }
    }

    @IBAction func nextDay(_ sender: Any) {
        if displayDay != numDays! - 1 {
            displayDay += 1
            configureDayButtons()
            reloadData()
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        if schedule.count != 0 {
            return schedule[displayDay].events.count
        } else {
            return 0
        }
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let dayItem = schedule[displayDay]
        print("display day = \(displayDay)")
        let eventItem = dayItem.events[indexPath.row]
        let identifier = eventItem.identifier
        let eventOrTimeText = eventItem.event

        let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
        let label = cell.viewWithTag(1000) as! UILabel
        label.text = eventOrTimeText
        if identifier == "event" || identifier == "eventDDI" {
            label.adjustsFontSizeToFitWidth = true
        } else if identifier == "time" || identifier == "location" {
           label.sizeToFit()
        }

        return UITableViewCell()

    }

    override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
        self.tableView(self.tableView, didSelectRowAt: indexPath)
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        if schedule[displayDay].events[indexPath.row].information != nil {
            let message = schedule[displayDay].events[indexPath.row].information
            let alert = UIAlertController(title: schedule[displayDay].events[indexPath.row].event, message: message, preferredStyle: .alert)
            let action = UIAlertAction(title: "Close", style: .default, handler: nil)
            alert.addAction(action)
            present(alert, animated: true, completion: nil)
        }
    }

    @IBAction func close(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }

    @IBAction func refresh(_ sender: Any) {
        scrapeSchedule { schedule in
            self.schedule = schedule!
            DispatchQueue.main.async {
                self.reloadData()
                self.numDays = self.schedule.count
                self.configureDayButtons()
            }
        }
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
       // if schedule.
        if schedule.count != 0 {
            let event = schedule[displayDay].events[indexPath.row]
            if event.identifier == "time" && event.event.count > 51 {
                return 75
            } else if event.identifier == "time" && event.event.count > 31 {
                let additionalChars = event.event.count - 31
                var quotient: Float = Float(additionalChars) / 20.0
                quotient = quotient.rounded(.up)
                return CGFloat(quotient * 14 + 40)
            }
        }
        return 34
    }

    func scrapeSchedule(completion: @escaping ([DayItem]?) -> Void) {
        var schedule = [DayItem]()
        URLCache.shared.removeAllCachedResponses()
        Alamofire.request("my api that functions correctly", method: .get).validate().responseData { response in
            switch response.result {
            case .success(let data):
                do {
                    let json = try JSON(data: data)
                    for (index,day):(String, JSON) in json {
                        schedule.append(self.organizeScheduleJSON(dayJSON: day))
                    }
                    completion(schedule)
                } catch {
                    completion(nil)
                }
            case .failure(let error):
                print(error.localizedDescription)
                completion(nil)
            }
        }
    }

    func organizeScheduleJSON(dayJSON: JSON) -> DayItem {
        var events = [EventItem]()
        for (index,event):(String, JSON) in dayJSON["eventTimes"] {
            if event["information"] != nil {
                events.append(EventItem(event: event["event"].string!, identifier: "eventDDI", information: event["information"].string!))
            } else {
                events.append(EventItem(event: event["event"].string!, identifier: "event"))
            }
            if event["location"] != nil {
                events.append(EventItem(event: event["location"].string!, identifier: "location"))
            }
            if event["times"] != nil {
                var timeArray = event["times"].arrayValue.map({$0.stringValue})
                for time in timeArray {
                    events.append(EventItem(event: time, identifier: "time"))
                }
            }
        }
        return DayItem(day: dayJSON["day"].string!, events: events)

    }
}

您的模块如下所示:

fun changeAddress(address: String) {
    val component = DaggerAppComponent.builder() //Assign this to wherever we want to keep a handle on the component
            .geoModule(GeoModule(address))
            .build()
    component.inject(this) //To reinject dependencies
}

但是,如果要在组件中创建许多不同的对象,则此方法可能很浪费。