Integrating Forecasts into superapp

Ok, we have a forecast now, and we need to use it somehow. Since it is a mood app, and weather affects mood more than temperature, we are interested in showing this info next to the mood in a MoodCell.

To do this, we will need to update DB schema again: we will create forecast object, and will create a relation between it and a mood object: they will have a one-to-one relationship, which means each mood will be able to have one forecast object and vice-versa. forecast object will have following properties:

We also need to update Mood schema, and add forecast object to it:
        #import <Realm/Realm.h>

        @class Forecast;

        @interface Mood : RLMObject

        @property NSDate    *created_at;
        @property NSString  *comment;
        @property NSInteger  level;
        @property NSInteger  id;

        @property Forecast *forecast;

        @end

        @interface Forecast : RLMObject

        @property NSDate    *created_at;
        @property NSInteger  latitude;
        @property NSInteger  longitude;
        @property NSInteger  temperature;
        @property NSString  *summary;
        @property NSInteger  id;

        @property Mood *mood;

        @end

        @implementation Mood
        @end

        @implementation Forecast
        @end
      
Since we have no forecast objects yet, migration code will be empty:
        def migrate_db
          next_id = 1
          RLMRealmConfiguration.migrate_to_version(2) do |migration, old_version|
            migration.enumerate "Mood" do |old_object, new_object|
              # update object to 1st schema version:
              if old_version < 1
                # migrate the object to 1st version
                new_object["id"] = next_id

                # increment ID for a next record:
                next_id += 1
              end

              if old_version < 2
                # empty migration to add new Forecast model
              end
            end
          end
        end
      

Do you remember the small problem with ID fields we had with Mood class? We needed some unique field that would represent each Mood object, and we created ID. Then we added Mood.next_id method that would assign a unique ID to each new object.

Since Forecast object needs ID as well, we will need to implement the same method for Forecast class as well. But in order not to repeat ourselves, we will create a new BaseObjectMethods module that will have a self.next_id method, and that will set ID on each object to a unique value. And then we will use it in both Mood and Forecast classes. Let's create /app/models/_base.rb first, and update our three files accordingly:

        # /app/models/_base.rb:
        module BaseObjectMethods

          # use methods described in ClassMethods as a class methods:
          def self.included(base)
            base.extend(ClassMethods)
          end

          # class methods should be described in the module below:
          module ClassMethods
            def default_values
              {
                "id" => self.next_id
              }
            end

            def next_id
              # get max ID and increment it:
              self.all.maxOfProperty("id").to_i + 1
            end
          end
        end

        # /app/models/forecast.rb:
        class Forecast
          include MotionRealm
          include AppConstants
          include BaseObjectMethods

          def self.for_location(location, on_date: date, &block)
            # ...
          end

          # rest of Forecast code ...
        end

        # /app/models/mood.rb:
        class Mood
          include MotionRealm
          include BaseObjectMethods
        end
      

Now when we have Forecast schema created and set up, we can start creating new Forecast objects each time user creates new Mood objects in MoodSelectorScreen.

        class MoodSelectorScreen < PM::Screen
          title "Mood Selector"

          def set_mood(level)
            if @forecast.is_a?(Hash) && @location.is_a?(Hash)
              mood = Mood.create level: level, created_at: NSDate.now
              forecast = Forecast.create summary: @forecast[:summary],
                temperature: @forecast[:temperature],
                latitude: @location[:lat],
                longitude: @location[:lng]
              forecast.mood = mood
              mood.forecast = forecast
            else
              UIAlertController.alert self, "Error",
                message: "Forecast is not downloaded yet. Please try again in a second",
                buttons: ["OK"]
              return
            end

            RLMRealm.write do |realm|
              realm << mood

              if forecast
                realm << forecast
              end
            end

            close
          end

          # rest of the code ...
        end
      
If forecast data is not ready yet, we will show an alert. It is not a good strategy - in a real app, you would need at least to show some loading indicator.

Let's try to launch the app and create a new mood. When done, just try to check it in console, whether it has a forecast attached, and whether forecast has correct data:

Now we need to update MoodHistoryScreen to show forecast in each Mood cell. We will update def tableView(table, cellForRowAtIndexPath: index) methods that render each cell to provide weather info to the cell, and set_table_data method to provide weather info:

        class MoodHistoryScreen < PM::Screen
          title "Mood History"

          # MoodHistoryScreen code ...

          def set_table_data

            # set_table_data code ...

            moods.each do |mood|

              # Figure out what part of the day was on the time of adding a mood:
              mood_hour = mood.created_at.hour
              if mood_hour >= 0 && mood_hour < 14
                day_part = "Morning"
              elsif mood_hour >= 14 && mood_hour < 20
                day_part = "Day"
              else
                day_part = "Evening"
              end

              # NEW:
              # add forecast's weather image:
              if mood.forecast.is_a?(Forecast)
                weather_icon = mood.forecast.icon
              else
                weather_icon = nil
              end

              # create a cell from a given mood object and calculated part of the day:
              section[:cells] << {
                date: day_part,
                mood: mood.level,
                comment: mood.comment,
                id: mood.id,
                # NEW:
                # include weather image:
                weather_icon: weather_icon,
              }
            end

            # rest of the set_table_data code
          end

          def tableView(table, cellForRowAtIndexPath: index)
            # ...

            # set cell's title to our item's title
            cell.main_text = item[:date]
            cell.mood_text = item[:mood]
            cell.weather_icon = item[:weather_icon]

            # return cell to the table so that it can render it:
            cell
          end

          # rest of the MoodHistoryScreen ...
        end
      

And the last thing to update is the cell and its layout. It will show different icons according to weather_icon variable. First we will save image view into an instance variable @weather_icon to access it in the future, and then we will create weather_icon method to set a picture:

        # /app/cells/mood_history_cell.rb
        class MoodHistoryCell < UITableViewCell
          def initWithStyle(style, reuseIdentifier:reuseIdentifier)
            super

            @layout = MoodHistoryCellLayout.new(root: self.contentView).build
            @main_text = @layout.get(:title)
            @mood_text = @layout.get(:mood)
            @weather_icon = @layout.get(:weather_icon)

            self
          end

          # rest of the cell code ...
          def weather_icon=(icon)
            return if !icon.is_a?(UIImage)
            @weather_icon.image = icon
          end
        end
      
Layout will be simple for now. We will show weather icon on the right side of a mood label:
        # /app/layouts/cells/mood_history_layout.rb
        class MoodHistoryCellLayout < MotionKit::Layout
          def layout
            root :cell do
              add UILabel, :title
              add UILabel, :mood
              add UIImageView, :weather_icon
            end
          end

          # rest of the layout code ...

          def weather_icon_style
            constraints do
              center_y.equals(:superview, :center_y)
              right.equals(:mood, :left).minus(15)

              width(32)
              height(32)
            end
          end
        end
      

Finally, we can launch the app and create some new mood objects. Your MoodHistoryScreen will look similar to this now:

It is a good time to commit all changes. Let's do it with

        git add .
        git commit -m "Forecasts added"
      

Summary

In this chapter we added a new feature - the app can get and save forecasts with each new Mood objects. We have learned how to create One-to-One relationships in the Database, recalled how to create a new object in DB, worked with TableView, and more.

Book Index | Next