Androidパスワードマネージャーの開発

すべてにご挨拶。 Android向けの最初のモバイルアプリケーションの開発のストーリーをお伝えし、さまざまな詳細を共有したいと思います。 特に、使用されるアーキテクチャとツールについて説明します。







準備する



もちろん、すぐにそれを書き始めたわけではありませんでした。その前に、数か月間さまざまなAndroid開発クラスを経験し、本、記事を読み、他の有用な情報源から積極的に情報を吸収しました。 Android向けの開発を行っている(または既に行っている)方は、私にとって最も役立つものに注意を払いたいと思います。





最初の2つは、おそらく既に会ったことがあります。モバイルアプリケーションの開発を開始する方法に興味があれば、ここではAndroidの開発の非常に基本的な知識を得ることができます。 さらに興味深い-androidweekly毎週の電子メールニュースレターには、Androidの世界で起こっているすべての興味深いニュースや記事が掲載されています。 そして、2つの非常に便利なチャットです。1つ目は、Androidの開発に関する質問への回答を、2つ目は、アーキテクチャに関する具体的な質問です。 そして、最後になりましたが、Googleからのサンプルをリンクします。



アプリについて



開発の開始のずっと前に、私はすでにアプリケーションのテーマを決定し、多数のログイン、パスワードなどを保存するために非常に緊急の問題に取り組みました。



Playマーケットには同様のアプリケーションがたくさんあります。私は自分にとっての主な欠点を強調し、それらを考慮して、実装したい主な機能のリストを作成しました。





開発



そして今、実際には私のアプリケーションについてです。だから、ここで説明するアプリケーションのレベルをおおよそ理解できるように、Android Studioのプロジェクト構造です。



画像の代替 アーキテクチャとして、私はMVPを選択しました。私の意見では、Android開発の標準になりました。 また、大規模プロジェクトに最適なクリーンアーキテクチャ、より柔軟、さらに多くの抽象化もあります。 しかし、私にとっては、Activity / Fragmentですべてのコードの開発を開始してからMVPで書き直したので、既に多すぎました(気が変わるまで時間がありました)。



プロジェクトをパッケージに分割するためのいくつかのオプションがありますが、最も近いのは画面(機能)で分割することです。 つまり、ほとんどすべてのパッケージには、単一の画面を実装するためのクラスが含まれています。 少数を除いて:





ああ、メインツールと開発で使用したアプローチをリストするのを忘れました。 すでにMVPについて言及しています。 Dagger 2で実装されたDependency Injection(DI)は、おそらくAndroid開発の標準でもあります。 もちろん、MVPとDIが存在する場所、および単体テスト。 単体テストは、プレゼンターパッケージモデルのすべてのロジックと部分的なデータをカバーします。 バージョン管理システム(VCS)はもう1つ必要です。繰り返しますが、ペットプロジェクトでも、それなしでは開発できませんでした。 私はGitを使用しますが、最初はGitHubを使用しましたが、Bitbucketに移行したプライベートリポジトリについて考えると、すべてのコミットの履歴が保存された状態で数回クリックするだけですべてが起こります。



少なくともある種のコードを切望している人のために、アプリケーションのパスワードリスト画面の実装方法の概要を以下に示します。 最終的には次のようになります。







パスワードパッケージクラス:







マークアップとして、Android Studio自体が作成するほぼ標準のスクロールアクティビティが使用されます。



CollapsingToolbarLayoutのフルサイズは、タイトルの下のエントリが存在するカテゴリの画像です。



PasswordsActivityコード:
public class PasswordsActivity extends AppCompatActivity implements PasswordsFragment.OnPasswordListFragmentInteractionListener, PasswordFieldsFragment.OnPasswordFragmentInteractionListener { PasswordsContract.Presenter mPasswordsPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityUtils.enableScreenshots(this); setContentView(R.layout.activity_scrolling_passwords_list); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mPasswordsPresenter.onAddNewPasswordButtonClick(); } }); //Open passwords of category with that id String categoryId = getIntent().getStringExtra(PasswordsFragment.ARG_EXTRA_CATEGORY_ID); //In tablet mode open details of password with id String passwordId = getIntent().getStringExtra(PasswordFieldsFragment.ARG_EXTRA_PASSWORD_ID); PasswordsMvpController.createPasswordsViews(this, categoryId, passwordId); } @Override protected void onStop() { super.onStop(); if (isFinishing()) { Injector injector = Injector.getInstance(); injector.destroyPasswordsComponent(); if (ActivityUtils.isTablet(this)) { injector.destroyPasswordFieldsComponent(); } } } public void setPasswordsPresenter(PasswordsContract.Presenter passwordsPresenter) { mPasswordsPresenter = passwordsPresenter; } /** * Methods implemented from {@link PasswordsFragment.OnPasswordListFragmentInteractionListener} * * @param parentCategory category that must be displayed */ @Override public void showCategoryInfo(Category parentCategory) { setTitle(parentCategory.getTitle()); CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); VectorDrawableCompat vectorDrawableCompat = VectorDrawableCompat.create(getResources(), parentCategory.getPicture().getResId(), getTheme()); collapsingToolbarLayout.setContentScrim(vectorDrawableCompat); ImageView categoryImageView = (ImageView) findViewById(R.id.category_picture_imageView); categoryImageView.setImageResource(parentCategory.getPicture().getResId()); }
      
      







最初の行onCreate()は、ユーザー設定に応じて、スクリーンショットの撮影を禁止または許可するメソッドを呼び出します。



インテントを通じて、この画面に表示する必要があるすべての情報が保存されているカテゴリのIDを取得します。 PasswordMvpControllerは、スマートフォンまたはタブレットを決定し、対応するフラグメントを作成します。



onStop()で、アクティビティが永久に閉じるかどうかを確認し、閉じる場合はこの画面のコンポーネントを削除します。 フラグメントからの別の興味深いコールバック(以下のフラグメント自体について)。ここで、画像とカテゴリの名前を設定します。



私の場合、フラグメントはViewであり、したがって、データを表示するために必要なメソッドをPresenterに提供します。ただし、フラグメントには興味深いものはなく、メソッド自体は非常に単純です。



エントリのリストは、RecyclerViewを使用して実装されます。これは、Android Weeklyに間に合った記事に記載されています。 このアプローチのおかげで、アダプターは非常に軽量であり、ViewHolderにある不要なロジックで過負荷になりません。



今、最も興味深いのはプレゼンターです。 同じPasswordMvpControllerクラスで、Injectプレゼンターが作成されます。 プレゼンテーション自体に必要なものは次のとおりです。passwordViewフラグメント、categoryIdはカテゴリの一意の識別子、表示するレコード、および識別子によってこのカテゴリを取得するdataRepositoryです。



プレゼンターコード
 public class PasswordsPresenter implements PasswordsContract.Presenter { private final DataRepository mDataRepository; private PasswordsContract.View mPasswordsView; // On tablets we can have null categoryId when @Nullable String mCategoryId; Category mCurrentCategory; PasswordsPresenter(DataRepository dataRepository, PasswordsContract.View passwordsView, @CategoryId @Nullable String categoryId) { mDataRepository = dataRepository; mPasswordsView = passwordsView; mCategoryId = categoryId; } public void setupListeners(PasswordsContract.View passwordsView) { mPasswordsView = passwordsView; mPasswordsView.setPresenter(this); } /* * Methods implemented from {@Link PasswordsContract.Presenter} * */ @Override public void start() { if (Strings.isNullOrEmpty(mCategoryId)) { // TODO: 25.02.2017 on tablets we can have null category id when don't pick any category, need to show message about it return; } mDataRepository.getCategory(mCategoryId, new DataSource.LoadCategoryCallback() { @Override public void onCategoryLoaded(Category category) { mCurrentCategory = category; mPasswordsView.showPasswordsInList(category.mPasswords); mPasswordsView.showCategoryInfo(mCurrentCategory); } @Override public void onDataNotAvailable() { //RecycleView show empty data message by itself } }); } @Override public void onPasswordClick(Password password) { mPasswordsView.showDetailPasswordUi(password); } @Override public void onAddNewPasswordButtonClick() { mPasswordsView.showNewPasswordUi(mCategoryId); } @Override public void onSwapPasswords(int firstPosition, int secondPosition) { mDataRepository.swapPasswords(mCurrentCategory, firstPosition, secondPosition); mPasswordsView.swapPasswordInList(firstPosition, secondPosition); } @Override public void loadCategory(Category category) { mCurrentCategory = category; mPasswordsView.showCategoryInfo(category); mPasswordsView.showPasswordsInList(category.mPasswords); } /* * Helper methods that can be called from {@Link CategoriesTabletPresenter} to manipulate view in tablet mode * */ public void onCurrentCategoryDeleted() { mPasswordsView.showEmptyUi(); } }
      
      







プレゼンターのstart()メソッドはonResume()フラグメントで呼び出され、既にその中でDataRepositoryを参照し、受信時にカテゴリとリクエストを表示します。 原則として、すべての方法は非常に単純です(ここでもMVPを称えます)。それらを説明する理由はありません。



おわりに



それはどういうわけか1つの記事に収まるようにうまくいかず、コードに直接費やされる時間はほとんどありませんでした。 したがって、興味がある場合は、コメントにあなたの意見を書いてください。データをアプリケーションに保存する方法、データを暗号化する方法、および一般に、セキュリティに関連するすべてについての記事を書くときにそれらを考慮します(多くのコードがあります)。



All Articles