Возникли проблемы при попытке изменить архитектуру на MVVM с помощью Dagger - PullRequest
0 голосов
/ 20 февраля 2019

В настоящее время я изучаю архитектуру Android и пытаюсь создать простое приложение с использованием MVVM, следуя руководству Google .Я следовал этому уроку, пока не попал в кешированную часть (что для меня сейчас не важно)Проблема в том, что я получаю пару проблем, которые не могу решить.

Это мой фрагмент:

public class OutboundFragment extends Fragment {

    private OutboundFlightsViewModel viewModel;

    public OutboundFragment() {
        // Required empty public constructor
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        viewModel = ViewModelProviders.of(this).get(OutboundFlightsViewModel.class);
        viewModel.init();
        viewModel.getFlights().observe(this, flights -> {
            // Update UI.
        });
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_outbound, container, false);
    }

}

Это мой репозиторий:

@Singleton
public class FlightsRepository {

    public LiveData<Flights> getFlights() {

        final MutableLiveData<Flights> data = new MutableLiveData<>();

        ApiInterface apiService =
                ApiClient.getClient().create(ApiInterface.class);

        Call<Flights> call = apiService.getFlights();
        call.enqueue(new Callback<Flights>() {
            @Override
            public void onResponse(Call<Flights>call, Response<Flights> response) {
                data.setValue(response.body());
            }

            @Override
            public void onFailure(Call<Flights>call, Throwable t) {
                // Log error here since request failed
            }
        });

        return data;
    }
}

Это мой ViewModel:

public class OutboundFlightsViewModel extends ViewModel {

    private LiveData<Flights> flights;
    private FlightsRepository flightsRepo;

    @Inject
    public OutboundFlightsViewModel(FlightsRepository flightsRepo) {
        this.flightsRepo = flightsRepo;
    }

    public OutboundFlightsViewModel(){}

    public void init() {
        if (this.flights != null) {
            return;
        }
        if (flightsRepo != null) {
            flights = flightsRepo.getFlights();
        }
    }

    public LiveData<Flights> getFlights() {
        return this.flights;
    }
}

Это зависимости, которые у меня есть в моем файле Gradle:

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation "android.arch.lifecycle:extensions:1.1.1"
    implementation "android.arch.lifecycle:viewmodel:1.1.1"
    // Dagger
    implementation 'com.google.dagger:dagger:2.20'
    implementation 'com.google.dagger:dagger-android-support:2.20'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.20'
    // Retrofit, gson
    implementation 'com.google.code.gson:gson:2.8.2'
    implementation 'com.squareup.retrofit2:retrofit:2.0.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'
    // RecyclerView
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    // butter knife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

Наконец, это logcat, содержащий обе проблемы:

2019-02-20 13:19:12.864 3864-27637/? E/ExternalAccountType: Unsupported attribute viewStreamItemActivity
2019-02-20 13:19:13.069 1181-1181/? E/LoadedApk: Unable to instantiate appComponentFactory
    java.lang.ClassNotFoundException: Didn't find class "android.support.v4.app.CoreComponentFactory" on path: DexPathList[[],nativeLibraryDirectories=[/system/app/OPBackup/lib/arm64, /system/app/OPBackup/OPBackup.apk!/lib/arm64-v8a, /system/lib64, /system/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:169)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.LoadedApk.createAppFactory(LoadedApk.java:226)
        at android.app.LoadedApk.updateApplicationInfo(LoadedApk.java:346)
        at android.app.ActivityThread.handleDispatchPackageBroadcast(ActivityThread.java:5524)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at com.android.server.SystemServer.run(SystemServer.java:482)
        at com.android.server.SystemServer.main(SystemServer.java:322)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
2019-02-20 13:19:13.092 10596-10622/com.example.mguimaraes.maxmilhas E/libc: Access denied finding property "vendor.debug.egl.profiler"
2019-02-20 13:19:13.093 10596-10622/com.example.mguimaraes.maxmilhas E/libc: Access denied finding property "vendor.debug.prerotation.disable"
2019-02-20 13:19:13.073 1181-1181/? E/LoadedApk: Unable to instantiate appComponentFactory
    java.lang.ClassNotFoundException: Didn't find class "android.support.v4.app.CoreComponentFactory" on path: DexPathList[[],nativeLibraryDirectories=[/system/app/OPBackup/lib/arm64, /system/app/OPBackup/OPBackup.apk!/lib/arm64-v8a, /system/lib64, /system/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:169)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.LoadedApk.createAppFactory(LoadedApk.java:226)
        at android.app.LoadedApk.updateApplicationInfo(LoadedApk.java:346)
        at android.app.ActivityThread.handleDispatchPackageBroadcast(ActivityThread.java:5524)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at com.android.server.SystemServer.run(SystemServer.java:482)
        at com.android.server.SystemServer.main(SystemServer.java:322)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
2019-02-20 13:19:13.149 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.153 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.172 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.172 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.184 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.184 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.192 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.192 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.192 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.197 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.198 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.198 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.201 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.343 10596-10596/com.example.mguimaraes.maxmilhas E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.mguimaraes.maxmilhas, PID: 10596
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.arch.lifecycle.LiveData.observe(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.Observer)' on a null object reference
        at com.example.mguimaraes.maxmilhas.Fragments.OutboundFragment.onActivityCreated(OutboundFragment.java:30)
        at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:2460)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1483)
        at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
        at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
        at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
        at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
        at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
        at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2243)
        at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:654)
        at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:146)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1244)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1092)
        at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1622)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.HeaderScrollingViewBehavior.onMeasureChild(HeaderScrollingViewBehavior.java:95)
        at android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onMeasureChild(AppBarLayout.java:1556)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:803)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.support.v7.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:401)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:717)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2917)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1747)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2040)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1635)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7795)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1172)
        at android.view.Choreographer.doCallbacks(Choreographer.java:984)
        at android.view.Choreographer.doFrame(Choreographer.java:809)
2019-02-20 13:19:13.343 10596-10596/com.example.mguimaraes.maxmilhas E/AndroidRuntime:     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1158)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6863)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Я что-то упустил?Я шаг за шагом следовал инструкциям.

Ответы [ 2 ]

0 голосов
/ 20 февраля 2019

Было бы здорово, если бы вы опубликовали часть своего кода кинжала, чтобы увидеть, что происходит.Кажется, вы плохо вводите свой репозиторий.

С другой стороны, у вас есть некоторые проблемы с инициализацией ViewModel.Если вы отладите модель представления, вы увидите, что ваш LiveData всегда равен нулю, потому что ваш FlightsRepository не может быть введен.И вы пытаетесь наблюдать его в своем фрагменте, не проверяя его состояние -> NullPointerException.

Прежде всего, вы можете инициализировать переменную MutableLiveData, несмотря на ваш репозиторий:

    // View Model snippet
    private MutableLiveData<Flights> flights;
    private FlightsRepository flightsRepo;

    @Inject
    public OutboundFlightsViewModel(FlightsRepository flightsRepo) {
        this.flightsRepo = flightsRepo;
    }

    public OutboundFlightsViewModel(){}

    public void init() {
        flights = new MutableLiveData<Flights>;

        if (flightsRepo != null) {
            flights.postValue(flightsRepo.getFlights());
        }
    }

    public LiveData<Flights> getFlights() {
        return this.flights;
    }

В этом случае у вас есть пустые LiveData, которые можно наблюдать в вашем фрагменте, поэтому, когда вы исправляете инъекцию в хранилище и когда у нее есть значение для публикации, вы можете реагировать на эти изменения и делать все, что захотите.

Относительно классов Dagger:

Вам нужен ваш ApplicationComponent для привязки каждого модуля, который вы хотите предоставить:

@Singleton
@Component(modules = [(AndroidInjectionModule::class),  (BuildersModule::class), (RepositoryModule::class)])
interface ApplicationComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: AppController): Builder

        fun build(): ApplicationComponent
    }

    fun inject(app: AppController)
}

Ваш класс BuildersModule обеспечивает каждую активность, которая будет внедрена.Если в вашей деятельности есть Фрагмент, который необходимо ввести, он также должен быть здесь.

@Module
abstract class BuildersModule {

    @ContributesAndroidInjector(modules = [(YourViewModelModule::class)])
    internal abstract fun contributeYourActivity(): YourActivity
}

В этом модуле вы объявляете каждый репозиторий, который хотите внедрить.

@Module
class RepositoryModule {
    @Provides
    fun yourRepository(): YourRepository {
        return YourRepository()
    }
}

Это ваш модуль модели представления, вам нужно создать фабрику модели представления и предоставить ее для внедрения.

@Module
class YourViewModelModule {
    @Provides
    fun providesYourViewModelFactory(yourRepository: YourRepository): YourViewModelFactory {
        return YourViewModelFactory(yourRepository)
    }
}

Вам понадобятся фабрики модели представления для введения параметров в классы модели представления.,

class YourViewModelFactory(private val repository: YourRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(YourViewModel::class.java)) {
            return YourViewModel(repository) as T
        }

        throw IllegalArgumentException("unknown view model class")
    }
}

Это ваша деятельность, которая содержит ваш фрагмент.Чтобы внедрить ваш фрагмент, вам нужно реализовать HasSupportFragmentInjection.

class OutboundActivity : AppCompatActivity, HasSupportFragmentInjector {

   @Inject
   lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>

   override fun supportFragmentInjector(): AndroidInjector<Fragment> {
           return dispatchingAndroidInjector
    }

    override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           AndroidInjection.inject(this)
    }


}

Вот ваш фрагмент, теперь он может получать введенную вами фабрику моделей представлений.Обратите внимание, что теперь ваш ViewModelProvider принимает два параметра: this и viewModelFactory , чтобы ваша модель представления могла получать введенные параметры.


class OutboundFragment: Fragment {

    @Inject
    lateinit var viewModelFactory: YourViewModelFactory

    private var viewModel: OutboundFlightsViewModel

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        AndroidSupportInjection.inject(this)

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(OutboundFlightsViewModel.class);
        viewModel.init();
        viewModel.getFlights().observe(this, flights -> {
            // Update UI.
        });
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_outbound, container, false);
    }

}

И последнее, но не менее важное: вынеобходимо переопределить ваш метод onCreate в вашем классе приложения и инициализировать кинжал с его компонентами.Обратите внимание, что здесь вам нужно реализовать HasActivityInjection.

class AppController : Application(), HasActivityInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>


    override fun onCreate() {
        super.onCreate()

        // Registered a global instance of AppController
        appControllerInstance = this

        // Init Dagger
        DaggerApplicationComponent.builder()
                .application(this)
                .build()
                .inject(this)
     }

      override fun activityInjector(): AndroidInjector<Activity>? {
        return dispatchingAndroidInjector
    }
}

Извините, я писал это на Kotlin, как каждый день, и я забыл, что вы программировали на Java.Но не пугайтесь, это похоже, вы поймете без проблем.Дайте мне знать, если вы не можете понять что-нибудь.

0 голосов
/ 20 февраля 2019

Во-первых, чтобы использовать Dagger с ViewModel для внедрения зависимостей, вам необходимо создать реализацию ViewModelProvider.Factory:

@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown viewmodel class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Эта реализация предоставит экземпляры ViewModel подклассов.

Ну, теперь нам нужно настроить ViewModelModule, который будет предоставлять экземпляры.Но перед этим нам нужно создать аннотацию для идентификации типа ViewModel, который будет предоставлен:

@MapKey
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewModelKey {
    Class<? extends ViewModel> value();
}

@MapKey - это аннотация от Dagger, которая идентифицирует возвращаемый результаттип метода @ Provides`.

Теперь это наш ViewModelModule:

@Module
public interface ViewModelModule {

    @Binds
    ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);

    @Binds
    @IntoMap
    @ViewModelKey(OutboundFlightsViewModel.class)
    ViewModel bindOutboundFlightsViewModel(OutboundFlightsViewModel viewModel);

}

Хорошо, поэтому нам нужно настроить компонент:

@Singleton
@Component(modules = {
    AndroidSupportInjectionModule.class,
    ViewModelModule.class
    // other modules goes here
})
interface AppComponent extends AndroidInjector<App> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<App> {}

}

Обратите внимание, что есть определенный установленный модуль под названием AndroidSupportInjectionModule.Этот модуль является частью dagger.android, который содержит множество классов, облегчающих использование Dagger для Android.

Наконец, нам нужно инициализировать сгенерированный класс из Dagger для создания нашей зависимостиграфик:

public class App extends DaggerApplication {

    @Override
    protected AndroidInjector<? extends App> applicationInjector() {
        return DaggerAppComponent.builder().create(this);
    }

}

Обратите внимание, что класс App расширяется DaggerApplication вместо Application.DaggerApplication является частью пакета dagger.android.

Для полного примера у меня есть проект на моем Github, который использует MVVM + Dagger 2.

Проект: https://github.com/WellingtonCosta/android-mvvm-databinding

Кроме того, у меня также есть небольшая библиотека, которую можно легко использовать в Dagger с моделью Android View.

Проект: https://github.com/WellingtonCosta/viewmodel-dagger

Надеюсь, она вам поможет!

...