モバむルアプリに入力する増分デヌタを蚭蚈する方法

こんにちは私の名前は私がAndroidのチヌムリヌダヌです、ノィヌタ゜コロワでサヌフ。



モバむルアプリケヌションには、質問やアプリケヌションなど、耇雑な倚段階の入力を䌎うフォヌムがありたす。このような機胜の蚭蚈は、通垞、開発者にずっお頭痛の皮になりたす。画面間で倧量のデヌタが転送され、ハヌドリンクが圢成されたす。぀たり、このデヌタを誰に、誰に、どの順序で送信し、次にどの画面を開くかです。



この蚘事では、ステップバむステップの機胜の䜜業を敎理するための䟿利な方法を共有したす。その助けを借りお、画面間の接続を最小限に抑え、ステップの順序を簡単に倉曎するこずができたす新しい画面を远加し、それらの順序ずナヌザヌに衚瀺するロゞックを倉曎したす。







*この蚘事の「機胜」ずいう蚀葉は、論理的に接続され、ナヌザヌにずっお1぀の機胜を衚すモバむルアプリケヌションの䞀連の画面を意味したす。



通垞、モバむルアプリケヌションで質問祚に蚘入し、アプリケヌションを送信するこずは、いく぀かの連続した画面で構成されたす。ある画面のデヌタが別の画面で必芁になる堎合があり、回答によっおステップチェヌンが倉わる堎合がありたす。したがっお、ナヌザヌがデヌタを「ドラフト」で保存しお、埌でプロセスに戻るこずができるようにするず䟿利です。



倚くの画面が存圚する可胜性がありたすが、実際には、ナヌザヌは1぀の倧きなオブゞェクトにデヌタを入力したす。この蚘事では、1぀のシナリオである䞀連の画面を䜿甚しお䜜業を䟿利に敎理する方法を説明したす。



ナヌザヌが仕事に応募しおフォヌムに蚘入しおいるずしたしょう。途䞭で䞭断した堎合、入力したデヌタは䞋曞きに保存されたす。ナヌザヌが蚘入に戻るず、ドラフトの情報が自動的に質問のフィヌルドに眮き換えられたす。ナヌザヌはすべおを最初から蚘入する必芁はありたせん。



ナヌザヌが質問祚党䜓に蚘入するず、回答がサヌバヌに送信されたす。



アンケヌトの内容は次のずおりです。



  • ステップ1-名前、教育の皮類、仕事の経隓、

  • ステップ2-研究の堎所

  • ステップ3-あなた自身に぀いおの仕事たたぱッセむの堎所、

  • ステップ4-欠員が関心を持っおいる理由。









アンケヌトは、ナヌザヌが教育や仕事の経隓があるかどうかによっお倉わりたす。教育がない堎合は、孊習堎所を埋めるステップを陀倖したす。仕事の経隓がない堎合は、ナヌザヌに自分自身に぀いお少し曞いおもらいたす。







蚭蚈段階では、いく぀かの質問に答える必芁がありたす。



  • 機胜スクリプトを柔軟にし、ステップを簡単に远加および削陀できるようにする方法。

  • ステップを開いたずきに、必芁なデヌタがすでに入力されおいるこずを確認する方法たずえば、入り口の[教育]画面は、既知の教育タむプがフィヌルドの構成を再構築するのを埅っおいたす

  • 最終ステップの埌でサヌバヌに転送するためにデヌタを共通モデルに集玄する方法。

  • アプリケヌションを「ドラフト」に保存しお、ナヌザヌが入力を䞭断しお埌で戻るこずができるようにする方法。



その結果、次の機胜を取埗したいず考えおいたす。







䟋党䜓がGitHubのリポゞトリにありたす 



明らかな解決策



「フル省電力モヌド」で機胜を開発する堎合、最も明癜なこずは、アプリケヌションオブゞェクトを䜜成し、それを画面間で転送しお、各ステップで補充するこずです。



薄い灰色は、特定のステップで䞍芁なデヌタを瀺したす。同時に、最終的に最終的なアプリケヌションに入るために、それらは各画面に送信されたす。







もちろん、このすべおのデヌタを1぀のアプリケヌションオブゞェクトにパックする必芁がありたす。それがどのように芋えるか芋おみたしょう



class Application(
    val name: String?,
    val surname: String?,
    val educationType : EducationType?,
    val workingExperience: Boolean?
    val education: Education?,
    val experience: Experience?,
    val motivation: List<Motivation>?
)


だが

このようなオブゞェクトを䜿甚しお、䜙分な䞍芁な数のnullチェックでコヌドをカバヌする運呜にありたす。たずえば、このデヌタ構造はeducationType、[教育]画面でフィヌルドがすでに入力されおいるこずを保蚌するものではありたせん。



より良くする方法



デヌタ管理を別のオブゞェクトに移動するこずをお勧めしたす。これにより、各ステップぞの入力で必芁なnull䞍可胜なデヌタが提䟛され、各ステップの結果がドラフトに保存されたす。このオブゞェクトをむンタラクタヌず呌びたす。これは、Robert Martinの玔粋なアヌキテクチャのナヌスケヌスレむダヌに察応し、すべおの画面で、さたざたな゜ヌスネットワヌク、デヌタベヌス、前のステップのデヌタ、ドラフト提案のデヌタなどから収集されたデヌタを提䟛したす。



私たちのプロゞェクトでは、SurfではDaggerを䜿甚しおいたす。いく぀かの理由から、むンタラクタヌは通垞、スコヌプが@PerApplicationになりたす。これにより、むンタラクタヌはアプリケヌション内でシングルトンになりたす。実際、むンタラクタヌは、機胜内のシングルトンにするこずも、すべおのステップがフラグメントである堎合はアクティベヌションにするこずもできたす。それはすべお、アプリケヌションの党䜓的なアヌキテクチャに䟝存したす。



さらに䟋では、アプリケヌション党䜓に察しおむンタラクタヌの単䞀のむンスタンスがあるず想定したす。したがっお、スクリプトの終了時にすべおのデヌタをクリアする必芁がありたす。







タスクを蚭定するずき、䞀元化されたデヌタストレヌゞに加えお、アプリケヌションのステップの構成ず順序を簡単に管理できるように敎理したいず考えたした。ナヌザヌがすでに入力した内容に応じお、ステップを倉曎できたす。したがっお、もう1぀の゚ンティティであるシナリオが必芁です。圌女の責任範囲は、ナヌザヌが実行する必芁のある手順の順序を維持するこずです。



スクリプトずむンタラクタヌを䜿甚した段階的な機胜の線成により、次のこずが可胜になりたす。



  • スクリプトのステップを倉曎するのは簡単です。たずえば、実行䞭にナヌザヌがリク゚ストを送信したり、さらに情報が必芁な堎合にステップを远加したりできないこずが刀明した堎合、さらに䜜業を重ねるこずができたす。

  • 契玄の蚭定各ステップの入力ず出力に必芁なデヌタ。

  • ナヌザヌがすべおの画面を完了しおいない堎合は、アプリケヌションをドラフトに保存するように敎理したす。



䞋曞きで保存されたデヌタを画面に事前入力したす。



基本゚ンティティ



この機胜のメカニズムは次のずおりです。



  • ステップ、入力、および出力を蚘述するためのモデルのセット。

  • シナリオ-ナヌザヌが実行する必芁のあるステップ画面を説明する゚ンティティ。

  • InteractorProgressInteractor-珟圚アクティブなステップに関する情報を保存し、各ステップの完了埌に入力された情報を集玄し、新しいステップを開始するための入力デヌタを発行するクラス。

  • DraftApplicationDraft-入力された情報の保存を担圓するクラス。 



クラス図は、具䜓的な実装が継承するすべおの基瀎ずなる゚ンティティを衚したす。それらがどのように関連しおいるか芋おみたしょう。







シナリオ゚ンティティの堎合、アプリケヌションのシナリオに期埅されるロゞックを説明するむンタヌフェむスを蚭定したす必芁な手順のリストを含み、必芁に応じお前の手順を完了した埌に再構築したす。



アプリケヌションには、倚数の連続した画面で構成される耇数の機胜があり、それぞれが機胜や特定のデヌタに䟝存しないすべおの䞀般的なロゞックを基本クラスProgressInteractorに移動したす。



ナヌザヌが入力したデヌタをドラフトに保存する必芁がない堎合があるため、ApplicationDraftは基本クラスに存圚したせん。したがっお、ProgressInteractorの具䜓的な実装はドラフトで機胜したす。スクリヌンプレれンタヌもそれず察話したす。



基本クラスの特定の実装のクラス図







これらの゚ンティティはすべお、次のように盞互に、および画面プレれンタヌず盞互䜜甚したす。







クラスはかなり倚いので、蚘事の最初から機胜を䜿甚しお各ブロックを個別に分析したしょう。



手順の説明



最初のポむントから始めたしょう。手順を説明する゚ンティティが必芁です。



// ,   ,    

interface Step




ゞョブアプリケヌションの䟋の機胜の堎合、手順は次のずおりです。



/**
 *     
 */
enum class ApplicationSteps : Step {
    PERSONAL_INFO,  //  
    EDUCATION,      // 
    EXPERIENCE,     //  
    ABOUT_ME,       //  " "
    MOTIVATION      //     
}



たた、各ステップの入力デヌタを蚘述する必芁がありたす。これを行うために、意図された目的のために封印されたクラスを䜿甚したす-限定されたクラス階局を䜜成したす。







コヌドでどのように芋えるか
//   
interface StepInData


:



//,      
sealed class ApplicationStepInData : StepInData

//     
class EducationStepInData(val educationType: EducationType) : ApplicationStepInData()

//        
class MotivationStepInData(val values: List<Motivation>) : ApplicationStepInData()




同様の方法で出力を説明したす。







コヌドでどのように芋えるか
// ,   
interface StepOutData

//,    
sealed class ApplicationStepOutData : StepOutData

//    " "
class PersonalInfoStepOutData(
    val info: PersonalInfo
) : ApplicationStepOutData()

//   ""
class EducationStepOutData(
    val education: Education
) : ApplicationStepOutData()

//    " "
class ExperienceStepOutData(
    val experience: WorkingExperience
) : ApplicationStepOutData()

//   " "
class AboutMeStepOutData(
    val info: AboutMe
) : ApplicationStepOutData()

//   " "
class MotivationStepOutData(
    val motivation: List<Motivation>
) : ApplicationStepOutData()




未蚘入のアプリケヌションをドラフトに保持するずいう目暙を蚭定しなかった堎合は、これに限定するこずができたす。ただし、各画面は空で開くだけでなく、䞋曞きから入力するこずもできるため、ナヌザヌがすでに䜕かを入力しおいる堎合は、入力デヌタず䞋曞きからのデヌタの䞡方がむンタラクタヌからの入力に送られたす。



したがっお、このデヌタをたずめるには、別のモデルのセットが必芁です。䞀郚の手順では、入力するための情報を必芁ずせず、ドラフトからのデヌタのフィヌルドのみを提䟛したす



コヌドでどのように芋えるか
/**
 *     +   ,   
 */
interface StepData<I : StepInData, O : StepOutData>

sealed class ApplicationStepData : StepData<ApplicationStepInData,  ApplicationStepOutData> {
    class PersonalInfoStepData(
        val outData: PersonalInfoStepOutData?
    ) : ApplicationStepData()

    class EducationStepData(
        val inData: EducationStepInData,
        val outData: EducationStepOutData?
    ) : ApplicationStepData()

    class ExperienceStepData(
        val outData: ExperienceStepOutData?
    ) : ApplicationStepData()

    class AboutMeStepData(
        val outData: AboutMeStepOutData?
    ) : ApplicationStepData()

    class MotivationStepData(
        val inData: MotivationStepInData,
        val outData: MotivationStepOutData?
    ) : ApplicationStepData()
}




スクリプトに埓っお行動したす



手順の説明ず入力/出力デヌタが敎理されおいたす。次に、コヌドの機胜スクリプトでこれらの手順の順序を修正したしょう。シナリオ゚ンティティは、珟圚のステップの順序を管理する責任がありたす。スクリプトは次のようになりたす。



/**
 * ,     ,     
 */
interface Scenario<S : Step, O : StepOutData> {
    
    //  
    val steps: List<S>

    /**
     *     
     *        
     */
    fun reactOnStepCompletion(stepOut: O)
}


この䟋の実装では、スクリプトは次のようになりたす。



class ApplicationScenario : Scenario<ApplicationStep, ApplicationStepOutData> {

    override val steps: MutableList<ApplicationStep> = mutableListOf(
        PERSONAL_INFO,
        EDUCATION,
        EXPERIENCE,
        MOTIVATION
    )

    override fun reactOnStepCompletion(stepOut: ApplicationStepOutData) {
        when (stepOut) {
            is PersonalInfoStepOutData -> {
                changeScenarioAfterPersonalStep(stepOut.info)
            }
        }
    }

    private fun changeScenarioAfterPersonalStep(personalInfo: PersonalInfo) {
        applyExperienceToScenario(personalInfo.hasWorkingExperience)
        applyEducationToScenario(personalInfo.education)
    }

    /**
     *    -       
     */
    private fun applyEducationToScenario(education: EducationType) {...}

    /**
     *      ,
     *           
     */
    private fun applyExperienceToScenario(hasWorkingExperience: Boolean) {...}
}


スクリプトの倉曎は双方向である必芁があるこずに泚意しおください。ステップを削陀するずしたす。ナヌザヌが戻っお別のオプションを遞択した堎合は、ステップがスクリプトに远加されおいるこずを確認しおください。



たずえば、コヌドは䜜業経隓の有無に察する反応のように芋えたすか
/**
 *      ,
 *           
 */
private fun applyExperienceToScenario(hasWorkingExperience: Boolean) {
    if (hasWorkingExperience) {
        steps.replaceWith(
            condition = { it == ABOUT_ME },
            newElem = EXPERIENCE
        )
    } else {
        steps.replaceWith(
            condition = { it == EXPERIENCE },
            newElem = ABOUT_ME
        )
    }
}




Interactorのしくみ



ステップバむステップ機胜のアヌキテクチャの次の構成芁玠であるむンタラクタヌに぀いお考えおみたす。䞊で述べたように、その䞻な責任は、ステップ間の切り替えを凊理するこずです。ステップぞの入力に必芁なデヌタを提䟛し、出力デヌタをドラフト芁求に集玄したす。



むンタラクタヌの基本クラスを䜜成し、すべおのステップバむステップ機胜に共通の動䜜をその䞭に入れたしょう。



/**
 *      
 * S -  
 * I -    
 * O -    
 */
abstract class ProgressInteractor<S : Step, I : StepInData, O : StepOutData> 


むンタラクタヌは珟圚のシナリオで䜜業する必芁がありたす。シナリオが䞀連のステップを再構築できるように、次のステップの完了に぀いおむンタラクタヌに通知したす。したがっお、スクリプトの抜象フィヌルドを宣蚀したす。これで、特定のむンタラクタヌごずに独自の実装を提䟛する必芁がありたす。



// ,      
protected abstract val scenario: Scenario<S, O>


むンタラクタヌは、珟圚アクティブなステップの状態を保存し、次たたは前のステップに切り替える圹割も果たしたす。目的のフラグメントに切り替えるこずができるように、ステップの倉曎をルヌト画面に迅速に通知する必芁がありたす。これはすべお、むベントブロヌドキャスト、぀たりリアクティブアプロヌチを䜿甚しお簡単に敎理できたす。たた、むンタラクタヌのメ゜ッドは非同期操䜜ネットワヌクたたはデヌタベヌスからのデヌタのロヌドを実行するこずが倚いため、RxJavaを䜿甚しおむンタラクタヌずプレれンタヌず通信したす。このツヌルにただ慣れおいない堎合は、この䞀連の玹介蚘事をお読みください。 



珟圚のステップずスクリプト内のその䜍眮に぀いお画面に必芁な情報を説明するモデルを䜜成したしょう。



/**
 *         
 */
class StepWithPosition<S : Step>(
    val step: S,
    val position: Int,
    val allStepsCount: Int
)


むンタラクタヌでBehaviorSubjectを開始しお、新しいアクティブなステップに関する情報を自由に出力しおみたしょう。



private val stepChangeSubject = BehaviorSubject.create<StepWithPosition<S>>()


画面がこのむベントのストリヌムをサブスクラむブできるように、stepChangeSubjectのラッパヌであるパブリック倉数stepChangeObservableを䜜成したす。



val stepChangeObservable: Observable<StepWithPosition<S>> = stepChangeSubject.hide()


むンタラクタヌの䜜業䞭に、珟圚アクティブなステップの䜍眮を知る必芁があるこずがよくありたす。むンタラクタヌに別のプロパティcurrentStepIndexを䜜成し、getメ゜ッドずsetメ゜ッドをオヌバヌラむドするこずをお勧めしたす。これにより、件名からこの情報に簡単にアクセスできたす。



コヌドでどのように芋えるか
//   
private var currentStepIndex: Int
    get() = stepChangeSubject.value?.position ?: 0
    set(value) {
        stepChangeSubject.onNext(
            StepWithPosition(
                step = scenario.steps[value],
                position = value,
                allStepsCount = scenario.steps.count()
            )
        )
    }




機胜のむンタラクタヌの特定の実装に関係なく、同じように機胜する䞀般的な郚分を曞いおみたしょう。



むンタラクタヌを初期化およびシャットダりンしお、子孫で拡匵できるようにするためのメ゜ッドを远加したしょう。



初期化ずシャットダりンの方法
/**
 *   
 */
@CallSuper
open fun initProgressFeature() {
    currentStepIndex = 0
}

/**
 *   
 */
@CallSuper
open fun closeProgressFeature() {
    currentStepIndex = 0
}




ステップバむステップの機胜むンタラクタヌが実行する必芁のある機胜を远加したしょう。



  • getDataForStepステップS-ステップSぞの入力ずしおデヌタを提䟛したす。

  • completeStepstepOutO-O出力を保存し、スクリプトを次のステップに移動したす。

  • toPreviousStep-スクリプトを前のステップに移動したす。



最初の関数である入力デヌタの凊理から始めたしょう。各むンタラクタヌ自䜓が、入力デヌタを取埗する方法ず堎所を決定したす。これを担圓する抜象的なメ゜ッドを远加したしょう



/**
 *      
 */
protected abstract fun resolveStepInData(step: S): Single<out StepData<I, O>>




特定の画面のプレれンタヌの堎合、を呌び出すパブリックメ゜ッドを远加したす resolveStepInData() :



/**
 *     
 */
fun getDataForStep(step: S): Single<out StepData<I, O>> = resolveStepInData(step)


メ゜ッドをpublicにするこずで、このコヌドを単玔化できresolveStepInData()たす。この方法はgetDataForStep()、以䞋で説明するステップ完了方法ずの類䌌性のために远加されおいたす。



ステップを完了するために、同様に、特定の各むンタラクタヌがステップの結果を保存する抜象的なメ゜ッドを䜜成したす。



/**
 *      
 */
protected abstract fun saveStepOutData(stepData: O): Completable


そしお公的な方法。その䞭で、出力情報の保存ず呌びたす。終了したら、終了ステップからの情報に調敎するようにスクリプトに指瀺したす。たた、䞀歩前進しおいるこずをサブスクラむバヌに通知したす。



/**
 *       
 */
fun completeStep(stepOut: O): Completable {
    return saveStepOutData(stepOut).doOnComplete {
        scenario.reactOnStepCompletion(stepOut)
        if (currentStepIndex != scenario.steps.lastIndex) {
            currentStepIndex += 1
        }
    }
}


最埌に、前のステップに戻るメ゜ッドを実装したす。



/**
 *    
 */
fun toPreviousStep() {
    if (currentStepIndex != 0) {
        currentStepIndex -= 1
    }
}


ゞョブアプリケヌションの䟋のむンタラクタヌの実装を芋おみたしょう。芚えおいるように、この機胜ではドラフトリク゚ストにデヌタを保存するこずが重芁です。したがっお、ApplicationProgressInteractorクラスで、ドラフトの䞋に远加のフィヌルドを䜜成したす。



/**
 *    
 */
@PerApplication
class ApplicationProgressInteractor @Inject constructor(
    private val dataRepository: ApplicationDataRepository
) : ProgressInteractor<ApplicationSteps, ApplicationStepInData, ApplicationStepOutData>() {

    //  
    override val scenario = ApplicationScenario()

    //  
    private val draft: ApplicationDraft = ApplicationDraft()

    //  
    fun applyDraft(draft: ApplicationDraft) {
        this.draft.apply {
            clear()
            outDataMap.putAll(draft.outDataMap)
        }
    }
    ...
}


ドラフトクラスはどのように芋えるか
:



/**
 *  
 */
class ApplicationDraft(
    val outDataMap: MutableMap<ApplicationSteps, ApplicationStepOutData> = mutableMapOf()
) : Serializable {
    fun getPersonalInfoOutData() = outDataMap[PERSONAL_INFO] as? PersonalInfoStepOutData
    fun getEducationStepOutData() = outDataMap[EDUCATION] as? EducationStepOutData
    fun getExperienceStepOutData() = outDataMap[EXPERIENCE] as? ExperienceStepOutData
    fun getAboutMeStepOutData() = outDataMap[ABOUT_ME] as? AboutMeStepOutData
    fun getMotivationStepOutData() = outDataMap[MOTIVATION] as? MotivationStepOutData

    fun clear() {
        outDataMap.clear()
    }
}




芪クラスで宣蚀された抜象メ゜ッドの実装を始めたしょう。ステップ完了機胜から始めたしょう-それは非垞に簡単です。特定のタむプの出力を目的のキヌの䞋のドラフトに保存したす。



/**
 *      
 */
override fun saveStepOutData(stepData: ApplicationStepOutData): Completable {
    return Completable.fromAction {
        when (stepData) {
            is PersonalInfoStepOutData -> {
                draft.outDataMap[PERSONAL_INFO] = stepData
            }
            is EducationStepOutData -> {
                draft.outDataMap[EDUCATION] = stepData
            }
            is ExperienceStepOutData -> {
                draft.outDataMap[EXPERIENCE] = stepData
            }
            is AboutMeStepOutData -> {
                draft.outDataMap[ABOUT_ME] = stepData
            }
            is MotivationStepOutData -> {
                draft.outDataMap[MOTIVATION] = stepData
            }
        }
    }
}


次に、ステップの入力デヌタを取埗する方法を芋おみたしょう。



/**
 *     
 */
override fun resolveStepInData(step: ApplicationStep): Single<ApplicationStepData> {
    return when (step) {
        PERSONAL_INFO -> ...
        EXPERIENCE -> ...
        EDUCATION -> Single.just(
            EducationStepData(
                inData = EducationStepInData(
                    draft.getPersonalInfoOutData()?.info?.educationType
                    ?: error("Not enough data for EDUCATION step")
                ),
                outData = draft.getEducationStepOutData()
            )
        )
        ABOUT_ME -> Single.just(
            AboutMeStepData(
                outData = draft.getAboutMeStepOutData()
            )
        )
        MOTIVATION -> dataRepository.loadMotivationVariants().map { reasonsList ->
            MotivationStepData(
                inData = MotivationStepInData(reasonsList),
                outData = draft.getMotivationStepOutData()
            )
        }
    }
}


ステップを開くずき、2぀のオプションがありたす



  • ナヌザヌが初めお画面を開きたす。

  • ナヌザヌはすでに画面に入力しおおり、䞋曞きにデヌタを保存しおいたす。



䜕も入力する必芁のないステップに぀いおは、ドラフトからの情報ある堎合を枡したす。 



 ABOUT_ME -> Single.just(
            AboutMeStepData(
                stepOutData = draft.getAboutMeStepOutData()
            )
        )


前のステップのデヌタを入力ずしお必芁な堎合は、ドラフトから匕き出したす各ステップの最埌に必ず保存したす。同様に、画面党䜓に䜿甚できるoutDataにデヌタを転送したす。



EDUCATION -> Single.just(
    EducationStepData(
        inData = EducationStepInData(
            draft.getPersonalInfoOutData()?.info?.educationType
            ?: error("Not enough data for EDUCATION step")
        ),
        outData = draft.getEducationStepOutData()
    )
)


さらに興味深い状況もありたす。ナヌザヌがこの特定の欠員に関心を持っおいる理由を瀺す必芁がある最埌のステップでは、考えられる理由のリストをネットワヌクからダりンロヌドする必芁がありたす。これは、このアヌキテクチャで最も䟿利な瞬間の1぀です。リク゚ストを送信し、回答を受け取ったら、ドラフトのデヌタず組み合わせお、入力ずしお画面に送信できたす。画面は、デヌタの出所や収集しおいる゜ヌスの数を知る必芁さえありたせん。



MOTIVATION -> {
    dataRepository.loadMotivationVariants().map { reasonsList ->
        MotivationStepData(
            inData = MotivationStepInData(reasonsList),
            outData = draft.getMotivationStepOutData()
        )
    }
}




このような状況は、むンタラクタヌを介しお䜜業するこずを支持する別の議論です。ステップにデヌタを提䟛するために、Webからのダりンロヌドや前のステップの結果など、いく぀かのデヌタ゜ヌスを組み合わせる必芁がある堎合がありたす。



私たちの方法では、倚くの゜ヌスからのデヌタを組み合わせお、必芁なものすべおを画面に提䟛できたす。この䟋でこれが優れおいる理由を理解するのは難しいかもしれたせん。実際の圢匏ではたずえば、ロヌンを申請する堎合、画面には、倚くのリファレンスブック、内郚デヌタベヌスからのナヌザヌに関する情報、5ステップ前に入力したデヌタ、および1970幎からの最も人気のある逞話のコレクションを送信する必芁がありたす。



プレれンタヌコヌドは、結果デヌタたたぱラヌのみを生成する別のむンタラクタヌメ゜ッドによっお集蚈が行われる堎合、はるかに簡単になりたす。すべおを探す堎所がすぐに明確になれば、開発者は倉曎や調敎を行うのが簡単になりたす。



しかし、むンタラクタヌにあるのはそれだけではありたせん。もちろん、すべおのステップが完了したずきに、最終的なアプリケヌションを送信するメ゜ッドが必芁です。最終的なアプリケヌションず、「ビルダヌ」パタヌンを䜿甚しおそれを䜜成する機胜に぀いお説明したしょう。



最終申請曞提出クラス
/**
 *  
 */
class Application(
    val personal: PersonalInfo,
    val education: Education?,
    val experience: Experience,
    val motivation: List<Motivation>
) {

    class Builder {
        private var personal: Optional<PersonalInfo> = Optional.empty()
        private var education: Optional<Education?> = Optional.empty()
        private var experience: Optional<Experience> = Optional.empty()
        private var motivation: Optional<List<Motivation>> = Optional.empty()

        fun personalInfo(value: PersonalInfo) = apply { personal = Optional.of(value) }
        fun education(value: Education) = apply { education = Optional.of(value) }
        fun experience(value: Experience) = apply { experience = Optional.of(value) }
        fun motivation(value: List<Motivation>) = apply { motivation = Optional.of(value) }

        fun build(): Application {
            return try {
                Application(
                    personal.get(),
                    education.getOrNull(),
                    experience.get(),
                    motivation.get()
                )
            } catch (e: NoSuchElementException) {
                throw ApplicationIsNotFilledException(
                    """Some fields aren't filled in application
                        personal = {${personal.getOrNull()}}
                        experience = {${experience.getOrNull()}}
                        motivation = {${motivation.getOrNull()}}
                    """.trimMargin()
                )
            }
        }
    }
}




アプリケヌション自䜓を送信する方法



/**
 *  
 */
fun sendApplication(): Completable {
    val builder = Application.Builder().apply {
        draft.outDataMap.values.forEach { data ->
            when (data) {
                is PersonalInfoStepOutData -> personalInfo(data.info)
                is EducationStepOutData -> education(data.education)
                is ExperienceStepOutData -> experience(data.experience)
                is AboutMeStepOutData -> experience(data.info)
                is MotivationStepOutData -> motivation(data.motivation)
            }
        }
    }
    return dataRepository.loadApplication(builder.build())
}


画面䞊ですべおを䜿甚する方法



ここで、プレれンテヌションレベルに移動しお、スクリヌンプレれンタヌがこのむンタラクタヌずどのように察話するかを確認する䟡倀がありたす。



私たちの機胜は、フラグメントのスタックを内郚に持぀アクティビティです。







アプリケヌションの送信が成功するず、別のアクティビティが開き、送信の成功に぀いおナヌザヌに通知されたす。メむンアクティビティは、むンタラクタヌのコマンドに応じお、目的のフラグメントを衚瀺し、ツヌルバヌですでに実行されたステップ数を衚瀺する圹割を果たしたす。これを行うには、ルヌトアクティビティプレれンタヌで、むンタラクタヌからサブゞェクトをサブスクラむブし、スタック内のフラグメントを切り替えるためのロゞックを実装したす。



progressInteractor.stepChangeObservable.subscribe { stepData ->
    if (stepData.position > currentPosition) {
        //      FragmentManager
    } else {
        //   
    }
    //   -    
}


各フラグメントのプレれンタヌで、画面の開始時に、むンタラクタヌに入力デヌタを提䟛するように䟝頌したす。前述のように、受信デヌタはネットワヌクからのダりンロヌドに関連付けるこずができるため、受信デヌタを別のストリヌムに転送するこずをお勧めしたす。



たずえば、教育情報を入力するための画面を芋おみたしょう。



progressInteractor.getDataForStep(EducationStep)
    .filter<ApplicationStepData.EducationStepData>()
    .subscribeOn(Schedulers.io())
    .subscribe { 
        val educationType = it.stepInData.educationType
 // todo:         

 it.stepOutData?.education?.let {
       // todo:      
  }
    }


「教育に぀いお」のステップを完了し、ナヌザヌがさらに進んでいきたいずしたす。出力を䜿甚しおオブゞェクトを圢成し、それをむンタラクタヌに枡すだけです。



progressInteractor.completeStep(EducationStepOutData(education)).subscribe {
                   //     ( )
               }


むンタラクタヌはデヌタ自䜓を保存し、必芁に応じおスクリプトの倉曎を開始し、ルヌトアクティビティに次のステップに切り替えるように通知したす。したがっお、フラグメントはスクリプト内での䜍眮に぀いお䜕も知りたせん。たずえば、機胜の蚭蚈が倉曎された堎合、フラグメントは簡単に再配眮できたす。



最埌のフラグメントでは、デヌタの保存が成功したこずぞの反応ずしお、最埌のリク゚ストの送信を远加したす。芚えおいるようsendApplication()に、むンタラクタヌでこのためのメ゜ッドを䜜成したした。



progressInteractor.sendApplication()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                {
                    //    
                    activityNavigator.start(ThankYouRoute())
                },
                {
                    //  
                }
            )


アプリケヌションが正垞に送信されたずいう情報が衚瀺された最埌の画面で、むンタラクタヌをクリアしお、プロセスを最初から再開できるようにしたす。



progressInteractor.closeProgressFeature()


それで党郚です。5぀の画面で構成される機胜がありたす。「教育に぀いお」の画面はスキップでき、仕事の経隓を蚘入した画面は、゚ッセむを曞くための画面に眮き換えられたす。任意のステップで入力を䞭断しお埌で続行でき、入力したものはすべおドラフトに保存されたす。プロゞェクトでこのアプロヌチを最初に実装した著者である



VasyaBeglyanin @ icebailに特に感謝したす。たた、Misha Zinchenko @ midery-ドラフトアヌキテクチャを最終バヌゞョンにするためのヘルプこの蚘事で説明。



All Articles