Medic Gem Version Build Status Code Climate Dependency Status

Is HealthKit's verbose and convoluted API driving you mad? Quick! You need a medic!

Installation

  1. Add this line to your application's Gemfile:
  gem 'medic'
  1. Add the following lines to your Rakefile:
  app.entitlements['com.apple.developer.healthkit'] = true
  app.frameworks += ['HealthKit']
  1. Run bundle and rake.

Usage

Authorization

To request authorization to read or share (i.e. write) a data type implement the following in viewDidAppear of your UIViewController:

if Medic.available?
  types = { share: :step_count, read: [:step_count, :date_of_birth] }

  Medic.authorize types do |success, error|
    NSLog "Success!" if success
  end
end

This will open the permissions modal. success will be false if the user canceled the prompt without selecting permissions; true otherwise.

You can subsequently check if you're authorized to share a data type:

Medic.authorized?(:step_count)

Note: For privacy reasons Apple does not allow you to check if you're authorized to read data types.

Sharing Samples

Coming soon...

Reading Data

HealthKit provides a number of methods for accessing its data, mostly in the form of query objects with verbose initializers that return more HKObjects with repetitive method names. For example, if I wanted to get the total number of steps taken per day for the last week I could use a HKStatisticsCollectionQuery like so:

@store = HKHealthStore.new

today = NSDate.date
one_week_ago = NSCalendar.currentCalendar.dateByAddingComponents(NSDateComponents.new.setDay(-7), toDate: today, options: 0)

query = HKStatisticsCollectionQuery.initWithQuantityType(
          HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount),
          quantitySamplePredicate: nil,
          options: HKStatisticsOptionCumulativeSum,
          anchorDate: one_week_ago,
          intervalComponents: (NSDateComponents.new.day = 1)
        )

query.initialResultsHandler = ->(query, results, error){
  results.enumerateStatisticsFromDate(one_week_ago, toDate: today, withBlock: ->(result, stop){
    if quantity = result.sumQuantity
      NSLog quantity.doubleValueForUnit(HKUnit.countUnit).to_s
    end
  })
}

@store.executeQuery(query)

This doesn't make for the most readable code. As a Ruby developer you might find this downright distasteful. Let's check out the Medic equivalent:

options = { options: :sum, anchor: :one_week_ago, interval: :day}

Medic.find_statistics_collection :step_count, options do |statistics|
  statistics.each do |stats|
    NSLog stats[:sum].to_s
  end
end

Now that's more like it! Instead of constructing HKObjectType by hand we can now just pass in a symbol. We also don't have to work directly with HKStatisticsCollection anymore. The result is parsed into an array of hashes with reasonable values for us.

Finders

Medic provides a finder for each class of HKQuery:

find_anchored

Provide an easy way to search for new data in the HealthKit store.

@anchor = nil

Medic.find_anchored :step_count, anchor: @anchor do |results, new_anchor|
  @anchor = new_anchor
  results.each do |sample|
    NSLog sample.to_s
  end
end
find_correlation

Search for correlations in the HealthKit store.

high_cal = HKQuantity.quantityWithUnit(HKUnit.kilocalorieUnit, doubleValue: 800.0)
greater_than_high_cal = HKQuery.predicateForQuantitySamplesWithOperatorType(NSGreaterThanOrEqualToPredicateOperatorType, quantity: high_cal)

sample_predicates = { dietary_energy_consumed: greater_than_high_cal }

Medic.find_correlations :food, sample_predicates: sample_predicates do |correlations|
  correlations.each do |correlation|
    NSLog correlation.to_s
  end
end
observe

Set up a long-running task on a background queue.

Medic.observe :step_count do |completion, error|
  Medic.find_sources :step_count do |sources|
    sources.each do |source|
      NSLog source
    end
  end
end
find_samples

Search for sample data in the HealthKit store.

Medic.find_samples :blood_pressure, sort: :start_date, limit: 7 do |samples|
  samples.each do |sample|
    NSLog sample.to_s
  end
end
find_sources

Search for the sources (apps and devices) that have saved data to the HealthKit store.

Medic.find_sources :step_count do |sources|
  sources.each do |source|
    NSLog source
  end
end
find_statistics

Perform statistical calculations over the set of matching quantity samples.

Medic.find_statistics :step_count, options: :sum do |statistics|
  NSLog statistics.to_s
end
find_statistics_collection

Perform multiple statistics queries over a series of fixed-length time intervals.

options = { options: :sum, anchor: :one_week_ago, interval: :day}

Medic.find_statistics_collection :step_count, options do |statistics|
  statistics.each do |stats|
    NSLog stats[:sum].to_s
  end
end

Characteristic Data

Characteristic data like biological sex or blood type have their own methods:

Medic.biological_sex # => :male
Medic.date_of_birth  # => 1987-11-07 00:00:00 -0800
Medic.blood_type     # => :o_negative

Queries

If for some reason you need to access the HKSample objects directly you can use Medic's QueryBuilder objects:

query_params = { type: :dietary_protein, sort_desc: :start_date, limit: 7 }

query = Medic::SampleQueryBuilder.new query_params do |query, results, error|
  if results
    results.each do |sample|
      NSLog "#{sample.startDate} - #{sample.quantity.doubleValueForUnit(HKUnit.gramUnit)}"
    end
  else
    NSLog "no results"
  end
end.query

Medic.execute(query)

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request