我的软件规格如下:
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)
这个问题有解决方案吗?
答案 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
}
但是,如果要在组件中创建许多不同的对象,则此方法可能很浪费。