TRILL Tech Blog

国内最大級の女性向けメディアTRILLの開発ブログです。

TRILLアプリのウィジェット設定方法

f:id:trill_tech:20201015160259p:plain:w300

ウィジェットはiOS14以降で利用できる機能です。OSをアップデートのうえご利用ください。

また、App StoreよりTRILLアプリバージョン3.5.0以降にアップデートしてください。

設定方法

1. ホーム画面で何も表示されていない部分を長押しし、上部の+ボタンをタップします

f:id:trill_tech:20201015160809p:plain:w300

2. TRILLアプリを選択します

f:id:trill_tech:20201015160824p:plain:w300

3. ウィジェットの種類を選択し、「ウィジェットを追加」をタップします

f:id:trill_tech:20201015161302p:plain:w300

4. ウィジェットを配置します

f:id:trill_tech:20201015160259p:plain:w300

TRILLのPdMって何してるの?

はじめまして。TRILL開発部PdMの米田です。
主にTRILLアプリ開発のマネジメントを担当しています。
TRILLというプロダクトの開発について、非技術者の視点であれこれご紹介できればと思います。

PdMと一口に言っても様々定義がある中で、今回は「TRILLのPdMって何をしてるの?」という話をしてみようと思います。

大まかに、何をしているの?

  • サービス全体のKPI目標達成に向け、施策を考える
  • 各所のステークホルダーとあれこれすり合わせる
  • 考えた施策たちの優先度を判断する
  • エンジニアとコミュニケーションをとり、開発タスクに落とす
  • 施策の効果を検証する
  • ↑これらのスケジュールを管理する

基本的にはこのサイクルをぐるぐる回すのが仕事です。

その過程においてトラブルが発生すればその対応を行ったり、チームの決まりごとを作ったりといった細々したものは発生しますが、基本的には施策を実行してサービスを開発視点で改善していく上で舵を切ることがメインの業務です。

施策を考える

施策には大きく2種類あると考えています。

ひとつは、プロダクトの「負」を解消し体験を良くするもの。
例えばアプリにおける各挙動の速度であったり、クラッシュを減らしたりといったものです。

ユーザーが

  • アプリを開き
  • 記事に出会い
  • 記事を読み
  • 別の記事に出会い
  • また記事を読み
  • 気に入った記事をお気に入りにストックし
  • 知人等に記事やアプリを薦める

といった一連の行動をいかにストレスなく行えるかを考え、日々コツコツ「負」を解消していっています。

そしてもうひとつが、数字を積み上げるための改善施策です。

事業として定めるKPIに対し、どこに大きな課題があり、その課題をどう改善していくかを開発視点で考えていきます。

ここは開発内だけでなく定例の場や日頃のやり取りの中でマーケチームに相談するようにしています。

施策を推し進める

日々出てくる課題に対しての打ち手(施策)が出たら、それらの優先度を判断してどこから手を付けるかを意思決定します。

この判断を誤ると、事業に対する成果やコストに影響が及ぶので、PdMにとって施策の優先度判断というのは非常に重要な業務です。

また、施策の優先度判断をする上で、他部署他職種の担当者などその施策に関わるステークホルダーとの調整が発生する場合があります。
TRILLにおいては、ここの調整を行うのもPdMの仕事です。

これらを踏まえ、サービス全体を俯瞰して何を優先すればよいかを考慮する必要があるため、開発以外の状況もある程度常にキャッチアップし、適切な判断を下す必要があります。(勉強不足を痛感する日々です)

開発する

施策の方針がある程度固まったら、エンジニアと話し合い、タスクに落としていきます。

便宜上ここで初めてエンジニアが登場していますが、もちろん前段階の施策の優先度を判断するタイミングでエンジニアに意見を求めたりということも頻繁にあります。(自分が非技術者ということもあり、判断に誤りがないようエンジニアとは非常に密なコミュニケーションをとっています)

ちなみにですが、TRILLのアプリ開発は2週で1スプリントのスクラムを採用しています。

少数での開発のため、以前は特にフレームワークに則らずよしなに開発を進めていたのですが、タスクが可視化されず管理がうまくいかなかったり、それによってスケジュール調整がうまくいかなかったりという問題がありました。

これらを解消すべく、エンジニアからの提案によってスクラムのフレームワークに則って開発を進めることにしました。

現在は比較的シンプルな開発フローが実現できています。

f:id:trill_tech:20201120134458p:plain

リリース・効果検証

スプリントバックログに積んだタスクは、開発を終えると新しいバージョンに載り、リリースされます。

TRILLではリリース作業自体はエンジニアが行いますが、リリースの責任はPdMが持つため、リリース内容は申請前のタイミングで必ず目を通します。

また「負」の解消にせよ積み上げの施策にせよ、開発したものは必ずリリース後にその効果を検証して省みる必要があります。

事前に定めた良し悪しの判断軸と照らし合わせて、次のアクションを検討していきます。ここまでが施策のワンセットです。

さいごに

こうして整理してみるとPdMとしてめちゃくちゃ特別ななにかをしているわけではありませんが、プロダクト開発の方向性を示し、ひとつひとつの判断に責任を持つという点でとても意味のあるポジションだという自覚を持っています。

そしてエンジニアをはじめとしたチームメンバーと肩を組み、スピード感をもった開発ができています。

もしTRILLの開発にご興味をお持ちいただけた方は、下記よりご連絡ください。ぜひ一度お話ししましょう!

積極募集中

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com

社内ライブラリをSwiftPMに対応させる

TRILL開発部の石田です。

TRILLでは、Swagger Codegenで生成したAPIクライアントライブラリを使ってサーバと通信しています。 このライブラリはGitHubで管理しており、Carthage経由で利用しています。

Xcode11からSwift Package Manager (以下SwiftPM) がサポートされたということで、上記ライブラリをSwiftPMに対応させてみました。

Swagger Codegen製APIクライアントライブラリ

Swaggerは、REST APIを記述するための仕様であり、その仕様からクライアントのライブラリや、サーバのスタブを自動生成するツールがSwagger Codegenです。 TRILLのクライアントアプリでは、Swagger Codegenで生成されたAPIクライアントライブラリを使っています。 iOSのクライアントライブラリは、内部でRxSwiftとAlamofireを使っており、そのためそれらライブラリと依存関係にあります。

SwiftPM対応

SwiftPM対応は、 Package.swift がルートディレクトリに存在し、GitHubなどのリモートリポジトリ経由でライブラリが参照できれば完了です。 Package.swift は以下のコマンドを実行することで生成されます。

$ cd MyPackage
$ swift package init

生成された Package.swift を必要に応じて編集します。 上述の通りRxSwiftとAlamofireと依存関係にあるので、 dependencies の部分に記載します。 また path の部分も必要に応じて編集します。

// swift-tools-version:5.2
import PackageDescription

let package = Package(
    name: "API",
    platforms: [
      .iOS(.v11)
    ],
    products: [
        .library(name: "API", targets: ["API"])
    ],
    dependencies: [
        .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "5.1.1"),
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9.1")
    ],
    targets: [
        .target(
            name: "API",
            path: "Source/Path",
            dependencies: [
                "RxSwift",
                "Alamofire"
            ]
        )
    ],
    swiftLanguageVersions: [.v5]
)

Package.swift の編集が完了したら、ビルドをします。

$ swift build

Package.resolved が生成されると思います。 これらファイルをまとめてGitHubなどにアップロードします。

Xcodeからの利用

Xcodeのメニューから、File → Swift Package → Add Package Dependency... から上記のライブラリを追加します。 プライベートリポジトリの場合は認証を必要としますが、GitHubのアカウント情報を入力すればダウンロードができます。

まとめ

Carthageで管理している社内ライブラリをSwiftPM対応しました。 Xcode公式のパッケージ管理ツールなので、信頼感がありますし、ソースコードもXcodeから確認することができるので便利に利用することができます。

しかし、Carthageのように事前のビルドがないため、クリーンビルドには時間がかかってしまいます。 そのため、最終的にはSwiftPM移行を諦め、現在はCarthageでの管理を行っています。 こちらに関しては、Xcode 12/Swift 5.3で対応したBinary Frameworkに期待したいと思います。


Google Apps Scriptを使ってBigQueryのクエリ結果をSlackに投稿する

TRILL開発部の石田です。

delyでは様々な情報をSlackに流して共有しているのですが、今回はTRILLで行っているBigQueryのクエリ結果のSlack投稿について紹介します。

背景

delyでは、透明性を大事にする取り組みとして、経営指標をオープンにSlackに流しています。

参考: dely会社紹介資料 / クラシルに関わるエンジニア・デザイナー募集 / dely - Speaker Deck

経営指標に限らず、アプリのパフォーマンス結果(クラッシュ率や速度など)を開発者だけでなくビジネスチームも含めて確認しています。

課題

TRILLではGoogleAnalyticsやFirebaseを使ってログを取得しています。 取得したログの結果は、GoogleAnalyticsとFirebaseの各管理画面から確認することができます。

しかし、欲しい情報を確認するためには管理画面を深く辿らなければならないことがあります。 また、もっと細かい粒度で分析するために、rawデータを使いたいときもあります。

そこで、GoogleAnalyticsやFirebaseのrawデータを加工してSlackに投稿することで、簡単に欲しい情報を確認できるようにしました。

やったこと

GoogleAnalyticsやFirebaseをBigQueryに連携し、BigQueryにrawデータを流し、BigQueryのクエリ結果をSlackに投稿するようにしました。 BigQueryのクエリ結果はGoogleスプレッドシートに書き込み、欲しい情報を溜めていくようにしています。

全体像

全体の構成は下図のようになります。

f:id:trill_tech:20200925175442p:plain

まず、Google Apps Script (以下GAS) からBigQueryにクエリを投げ、その結果をスプレッドシートに書き込みます。 次にスプレッドシートからデータを取得し、Slackに投稿します。 スプレッドシートにはデータが溜まっているので、先週比、先月比など所望の差分データを取り出すことができます。

BigQueryのクエリ結果をスプレッドシートに書き込む

まず、GASからBigQueryにアクセスできるようにする必要があります。 メニューの [リソース] → [Googleの拡張サービス] を選択し、BigQueryを有効にします。

以下のコードは、BigQueryのクエリ結果をスプレッドシートに書き込むサンプルとなります。 スプレッドシートのセルA1に、 COUNT(*) の結果が書き込まれます。

function runQuery() {
  var projectId = 'GCPのプロジェクトID';
  var sql = '\
    #standardSQL\n\
    SELECT COUNT(*)\
    FROM "BigQueryのテーブル名"';
  var resource = {query: sql};
  var queryResults = BigQuery.Jobs.query(resource, projectId);
  var jobId = queryResults.getJobReference().getJobId();

  while (!queryResults.getJobComplete()) {
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
    Utilities.sleep(1000);
  }

  var spreadsheetId = 'スプレッドシートのID';
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName('シート名');
  sheet.getRange('A1').setValue(queryResults.rows[0].f[0].v);
}

GASから実行するとスプレッドシートとBigQueryのアクセス許可ダイアログが表示され、許可すると対象のスプレッドシートにクエリ結果が書き込まれます。

スプレッドシートの値をSlackに投稿する

以下のコードは、スプレッドシートの値をSlackに投稿するサンプルとなります。 実行するとセルA1に書き込んだ値をSlackに投稿します。

function post() {
  var spreadsheetId = 'スプレッドシートのID';
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName('シート名');
  var result = sheet.getRange('A1').getValue();

  var data = {
    'text': result
  };
  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'payload' : JSON.stringify(data)
  };
  var webhookUrl = 'SlackのWebhook URL';
  UrlFetchApp.fetch(webhookUrl, options);
}

実際には、日毎にデータを集計しており、先週比、先月比でデータがどう変化したかを投稿しています。

まとめ

Google Apps Scriptを使ってBigQueryのクエリ結果をSlackに投稿する方法について紹介しました。 欲しい情報をrawデータから加工し、毎日Slackへ自動的に投稿することで、誰でも簡単に情報を取得することが出来ます。

delyでは全方面でエンジニアを積極採用中です。 興味のある方は是非お声がけください。

com.google.gms:oss-licenses でライセンス表記を実装してみた

どうも、Android担当の永井です。

TRILLでは、OSSのライセンス表記をHTMLに張り付けてWebViewに流し込むような運用をしていたけど、ライブラリ追加削除するたびにいちいち変更がめんどう!
とういうことで、Google謹製の com.google.gms:oss-licenses を導入してライセンス表記の編集作業とおさらばしました!

詳しい手順はこちら 
developers.google.comdevelopers.google.com
 
実作業はかんたん。
依存関係追加してActivityを呼び出すだけ。
あとは勝手にライセンス情報を取得してリスト表示してくれます。

・依存関係の追加
ルートレベルのbuild.gradleにoss-licensesプラグインを追加。

buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
}

 
・アプリレベルのbuild.gradleでプラグインを適用

apply plugin: 'com.google.gms.oss.licenses.plugin'

これで準備OK。
ビルドするとpomから依存するライブラリのライセンス情報を取得して一覧化してくれます。

あとは適当なところで画面を呼び出すだけ。
setActivityTitleでActionBarに表示するタイトルを変更できます。

    @OnClick(R.id.activity_information_title_license_tv)
    void onClickLicense() {
        startActivity(new Intent(this, OssLicensesMenuActivity.class));
        OssLicensesMenuActivity.setActivityTitle(getString(R.string.activity_setting_license_title));
    }


・画面のカスタマイズ
OssLicensesMenuActivity使うのであればできることはだいぶ少なくタイトル設定とテーマ変更くらいしかできなさそう。

マニフェストにテーマ指定して、

        <activity
            android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
            android:theme="@style/LicenseTheme" />
        <activity
            android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
            android:theme="@style/LicenseTheme" />

テーマで指定すればOK。
ActionBarいらないならこれで。
自分はこれにしました。

    <style name="LicenseTheme" parent="Theme.AppCompat.Light">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:statusBarColor">@color/gray_medium_light</item>
        <item name="android:textSize">@dimen/font_size_tiny</item>
    </style>

つかうならたぶんこんな感じである程度デザイン揃えられそう。

    <style name="LicenseTheme" parent="Theme.AppCompat.Light">
        <item name="windowActionBar">true</item>
        <item name="windowNoTitle">false</item>
        <item name="actionBarStyle">@style/LicenseTheme.ActionBar</item>
        <item name="android:actionBarStyle">@style/LicenseTheme.ActionBar</item>
        <item name="android:statusBarColor">@color/gray_medium_light</item>
        <item name="android:textSize">@dimen/font_size_tiny</item>
    </style>

    <style name="LicenseTheme.ActionBar" parent="Widget.AppCompat.Light.ActionBar">
        <item name="background">@color/white</item>
        <item name="android:background">@color/white</item>
        <item name="titleTextStyle">@style/LicenseTheme.ActionBar.TextStyle</item>
        <item name="android:titleTextStyle">@style/LicenseTheme.ActionBar.TextStyle</item>
    </style>

    <style name="LicenseTheme.ActionBar.TextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
        <item name="android:textSize">@dimen/font_size_tiny</item>
    </style>
</resources>


もっと細かくデザイン合わせた画面作りたければ、
ライセンス情報自体は、app/build/generated/third_party_licenses/res/raw ディレクトリに、third_party_licenses、third_party_license_metadataとして出力されているので、これを読み取って表示すればいろいろできそうです!




 

TRILLアプリでiOS14のWidgetに対応しました & Tips集

TRILL開発部の石田です。

TRILLでは、ver.3.5.0でiOS14で新しく登場したWidgetに対応しました。

f:id:trill_tech:20201007160943p:plain:w400

もともとToday Extensionには対応していたのですが、Widget Extensionは新しい機能ということでデザインや実装を見直しました。

Widget自体はWidgetKitフレームワークとSwiftUI用のウィジェットAPIを使って実装していくのですが、以下ではWidgetの実装で悩みやすい部分についてサンプル実装を紹介したいと思います。

サイズごとに別のViewを実装する

WidgetにはSmall、Medium、Largeの3種類のサイズがあります。

f:id:trill_tech:20201007161020p:plain

f:id:trill_tech:20201007161031p:plain

f:id:trill_tech:20201007161040p:plain

デフォルトでは、EntryViewで定義したViewがそれぞれのサイズに伸縮され描画されますが、WidgetFamilyを使って場合分けすることでサイズごとに別のViewを設定することができます。

例えば以下のようになります。

struct SampleWidgetEntryView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var entry: Provider.Entry

    @ViewBuilder
    var body: some View {
        switch family { 
        case .systemSmall: Text("Small")
        case .systemMedium: Text("Medium")
        case .systemLarge: Text("Large")
        default: Text("NotAvailable")
        }
    }
}

特定のサイズのみ対応する

上述の通り、Widgetには3種類のサイズがあります。 その中でもLargeには対応せず、SmallとMediumのみに対応したい場合があると思います。 そのときは、 supportedFamilies(_:))を使うことで、対応するサイズを定義することができます。

@main
struct SampleWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "widget", provider: Provider()) { entry in
            SampleWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

同じサイズで別のViewを設定する

Widgetは同じサイズでも別のViewを設定することができます。 例えば天気アプリのWidgetを考えたとき、Smallサイズでも「現在の天気」と「雨雲レーダー」の2種類を実装したいという場合です。 その場合、WidgetBundleを使い、Widgetを複数定義することで実装することができます。

@main
struct SampleWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        WeatherWidget()
        RainRadarWidget()
    }
}

定期実行する

ニュースアプリなどのWidgetでは、定期的にサーバにアクセスし、最新の情報をWidgetで提示したいと思います。 その場合、TimelineのTimelineReloadPolicyに所望の時間を設定することで実現できます。 下記は15分ごとに実行するサンプルとなります。

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    let currentDate = Date()
    let refreshDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)!
        
    ...
        
    let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
    completion(timeline)
}

Deep Linkを設定する

Widgetはデフォルトではタップするとアプリを開くだけです。 特定の画面に遷移させるにはwidgetURL(_:))を設定することで実現できます。

Smallサイズではタップ領域は1つだけですが、Mediumサイズ、Largeサイズには複数のタップ領域を設けることができます。 その場合、Linkを使うことで複数の導線を設定できます。

例えば下記のようになります。

var body: some View {
    VStack {
        Link(destination: hogeDeepLink) {
            Text("Hoge")
        }
        Link(destination: fugaDeepLink) {
            Text("Fuga")
        }
    }.widgetURL(piyoDeeplink)
}

まとめ

Widgetの実装において、「これってどうやるんだ?」という機能のサンプル実装を紹介しました。 Widgetはサイズが固定されており、機能的な制限もありますが、既存の機能とは異なる新しい価値をユーザに提供できるのではないかと思います。 まだ登場したばかり対応しているアプリは少ないですが、今後どのようなWidgetが出てくるかウォッチしながら、さらなるブラッシュアップをしていきたいと考えています。

TRILLではiOS限らずAndroid、サーバなどでエンジニアを積極採用中です。 Widgetなど新しい機能にも積極的に取り組めますので、ご興味がありましたら採用ページよりご連絡ください!


TRILLプロダクトについて

皆さん、こんにちは。

はじめましてTRILL開発部の永井です。

 

ここではTRILLのプロダクトにおいて取り入れている技術だったり、エンジニアの考えてることやナレッジを発信していけたらと思っています。

 

さて今回は初記事ということで、TRILLのプロダクトや開発環境について紹介したいと思います。

 

「TRILL」は、月間利用者数4000万MAU、アプリ累計DL1000万のまだまだ拡大を続けている国内No.1女性向けメディアで、「女性が元気に活躍する社会」というビジョンを掲げ、女性たちの自己実現をサポートできる存在でありたいと考えています。また圧倒的なリーチ力を生かして女性向けのサービスを展開するクライアントのマーケティング・ブランディング課題の解決を行なっています。

 

開発として、現在はユーザ規模を生かした広告事業を主軸にメディアとしてのアプリグロースを進めていますが、巨大メディアにとどまることなくTRILLがユーザから強烈に求められる価値を提供できるようなビジネスモデルへの「進化」も構想中ですので、こういった価値の実現に向け開発に取り組んでいます。

 

note.com

 

開発では以下の技術を採用しています。

アプリ

・Java、Kotlin、RxJava2、Material Design

・Swift、RxSwift、Fastlane

・MVP、Clean Architecture

・DI、ReactiveX、Retrofit、Realm

・Firebase、Repro

Web、BE

・Ruby、Rails、React、Node.js、Swagger

・MySQL、Elasticsearch、Redis

・GCP、AWS

その他

・GA、BigQuery、Adjust

・GitHub、Slack、DocBase、LGTM

 

これからもモダンな技術を取り入れ開発していく中で、そういった技術やノウハウをアウトプットしていければと思いますので、どうぞよろしくお願いします。

 

いかがでしたでしょうか。

女性向けメディア「TRILL」を運営するdelyは、更なる成長を目指して各ポジション採用を強化していきます!世の中の女性を元気にするメディアを一緒に作っていきたい方の応募お待ちしております。