How to properly use Dagger2 with the new Android Architecture Components - java

I'm trying to use the new Architecture Components, but I'm also still new to dagger and I'm missing stuff.
With the below code, I'm getting a NullPointerException, can't locate where.
Also if there's something else I need to fix or improve, please suggest.
CODE :
ViewModel
public class PostsVM extends ViewModel {
private LiveData<StoryPost> post;
private Repository repository;
#Inject
public PostsVM(Repository repository) {
this.repository = repository;
}
public void init() {
if (this.post != null) {
return;
}
post = repository.getPosts();
}
public LiveData<StoryPost> getPost() {
return post;
}
}
Repository
#Singleton
public class Repository {
private final MutableLiveData<StoryPost> data = new MutableLiveData<>();
public LiveData<StoryPost> getPosts() {
//
new GetUser(post.getUid()) {
#Override
public void onSuccess(#NonNull User user) {
// this is where I setValue//
data.setValue(post);
}
#Override
public void onError() {
}
#Override
public void userNotFound() {
}
};
return data;
}
}
Singleton Factory
#Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
#Inject
public 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 model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
DAO
#Dao
public interface PostDao {
#Query("SELECT * FROM posts ORDER by time DESC")
LiveData<List<StoryPost>> getAll();
#Query("SELECT * FROM posts WHERE id = :id")
LiveData<List<StoryPost>> getPost(String id);
#Insert(onConflict = OnConflictStrategy.REPLACE)
#NonNull
void insert(StoryPost... posts);
#Delete
void delete(StoryPost post);
#Update
void update(StoryPost post);
}
Then in MainActivity:
#Inject public ViewModelFactory factory;
//...
//*onCreate*
PostsVM model = ViewModelProviders.of(this, factory).get(PostsVM.class);
model.init();
final Observer<StoryPost> observer = post -> storyAdapter.insert(post);
model.getPost().observe(this, observer);
Logcat :
... java.lang.NullPointerException: Attempt to invoke interface method
'android.arch.lifecycle.ViewModel android.arch.lifecycle.ViewModelProvider
$Factory.create(java.lang.Class)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2479)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2539)
at android.app.ActivityThread.access$900(ActivityThread.java:168)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1378)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5665)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:799)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:689)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'android.arch.lifecycle.ViewModel android.arch.lifecycle.ViewModelProvider$Factory.create(java.lang.Class)' on a null object reference
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96)
at com.aollay.smartpaper.MainActivity.bindDatabase(MainActivity.java:238)
at com.aollay.smartpaper.MainActivity.populateNews(MainActivity.java:233)
at com.aollay.smartpaper.MainActivity.config(MainActivity.java:159)
at com.aollay.smartpaper.MainActivity.onCreate(MainActivity.java:74)
at android.app.Activity.performCreate(Activity.java:6372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2432)

The issue is caused by the ViewModelFactory instance being null inside your MainActivity, as the NPE suggests. This itself is most probably caused by the fact that the ViewModelFactory is not being injected properly, thus remaining null. As Orest suggests inside the comments, you need to make sure that the MainActivity is properly injected from your AppModule:
MainActivity:
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
AndroidInjection.inject(activity);
super.onCreate(savedInstanceState);
}
// if your Activity also has Fragments which need to be injected
#Inject
DispatchingAndroidInjector<Fragment> androidInjector;
#Override
public DispatchingAndroidInjector<Fragment> supportFragmentInjector()
{
return androidInjector;
}
}
You can take a look at most of the DI classes being used in a related question I posted earlier over at AndroidInjector<android.app.Activity> cannot be provided without an #Provides- or #Produces-annotated method and see if that setup helps you out.

Related

How to initialize a class and pass an object to the constructor in Android?

I have the following class:
public class MovieViewModel extends ViewModel {
private String query;
public MovieViewModel() {
MyFactory factory = new MyFactory(); //Pass the query in the constructor
//other stuff
}
}
And in my MainActivity, I'm creating an object of this class like this:
MovieViewModel model = ViewModelProviders.of(this).get(MovieViewModel.class);
How can I create an object of MovieViewModel class and in the same time I initialize the query String so it can be passed to the MyFactory constructor?
You can create custom Factory.
public class MovieViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
private String mParam;
public MovieViewModelFactory(Application application, String param) {
mApplication = application;
mParam = param;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new MovieViewModel(mApplication, mParam);
}
}
Now you can use like this
MovieViewModel movieViewModel = ViewModelProviders.of(this, new MovieViewModelFactory(this.getApplication(), "param")).get(MovieViewModel.class);
You have to provide a factory class :
public class MovieViewModel extends ViewModel {
private String query;
MovieViewModel(String objectToBePassed) {
//other stuff
}
static class MovieViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private String mObject;
public MovieViewModelFactory(String objectToBePassed) {
mObject = objectToBePassed;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T) new MovieViewModel(mObject);
}
}
}
and usage
ViewModelProviders.of(context,
new MovieViewModel.MovieViewModelFactory(object))
.get(MovieViewModel.class);

Dagger 2 field injection is not working in Android

The issue is that I try to use field injection with Dagger 2, but at runtime field, that should be injected, always is null. Also I try to use MVVM pattern. Here is my code:
ProfileActivity.java:
#Override
protected void onStart() {
super.onStart();
Log.d(TAG, "ProfileActivity: onStart: ");
final ProfileViewModel profileViewModel
= ViewModelProviders.of(this).get(ProfileViewModel.class);
profileViewModel.init();
profileViewModel.getUser().observe(this, new Observer<User>() {
#Override
public void onChanged(#Nullable User user) {
if (user != null) {
Log.d(TAG, "ProfileActivity: onStart: " + user.toString());
} else {
Log.d(TAG, "ProfileActivity: onStart: user == null");
}
}
});
}
ProfileViewModel.java:
public class ProfileViewModel extends ViewModel {
private LiveData<User> user;
#Inject
UserRepository userRepository;
public ProfileViewModel() {
Log.d(TAG, "ProfileViewModel: Constructor: ");
}
public void init() {
Log.d(TAG, "ProfileViewModel: init: ");
user = userRepository.getUser();
}
public LiveData<User> getUser() {
Log.d(TAG, "ProfileViewModel: getUser: ");
return user;
}
}
UserRepository.java:
#Singleton
public class UserRepository {
private LiveData<User> user;
#Inject
public UserRepository() {
Log.d(TAG, "UserRepository: Constructor: ");
}
public LiveData<User> getUser() {
Log.d(TAG, "UserRepository: getUser: ");
if (user != null) {
return user;
} else {
// There should be userDao.load() call,
// but it had been omitted for brevity.
MutableLiveData<User> user = new MutableLiveData<>();
user.setValue(DB.getUser());
return user;
}
}
}
MyApplication.java:
public class MyApplication extends MultiDexApplication implements HasActivityInjector {
#Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
DaggerMyApplicationComponent.create().inject(this);
}
#Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
MyApplicationModule.java:
#Module
public abstract class MyApplicationModule {
#ContributesAndroidInjector
abstract ProfileActivity contributeActivityInjector();
}
MyApplicationComponent.java:
#Component(modules = { AndroidInjectionModule.class, MyApplicationModule.class})
#Singleton
public interface MyApplicationComponent extends AndroidInjector<MyApplication> {
void inject(ProfileActivity profileActivity);
}
At runtime I can see the next logs:
ProfileActivity: onStart:
ProfileViewModel: Constructor:
ProfileViewModel: init:
And the app crashes on user = userRepository.getUser(); inside ProfileViewModel's init() method.
It means that UserRepository had not been injected. Also it is indicated by missing UserRepository: Constructor: log.
Where is my mistake? Thank you.
Basically what you need to do is to use ViewModel Factory to pass injected UserRepository into your ViewModels constructor, initialize it and then you will be able to use it. You cannot use field or parameter injections in ViewModels.
I would suggest you to follow this article: Add the new ViewModel to your MVVM
It provides enough sufficient information to begin using Dagger 2 with Architecture Components.
Hope it helps.

How to check typed callback type inside Fragment.onAttach()

I'm trying to implement abstract fragment with typed callback to use it in several subclasses.
How can I check if Context is instance of appropriate class?
My code of abstact CallbackFragment:
public abstract class CallbackFragment<C> extends Fragment {
protected C mCallback;
public CallbackFragment() {
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
//just in case
if(context == null)
throw new NullPointerException();
try {
mCallback = (C) context; //this line not seems to throw any exception
} catch (ClassCastException exception) {
throw new RuntimeException(context.toString() + " must implement Callbacks");
}
}
#Override
public void onDetach() {
super.onDetach();
mCallback = null;
}
}
Vehicle list fragment:
public abstract class VehicleListFragment<T extends Vehicle>
extends CallbackFragment<VehicleListFragment.Callback<T>> {
//callback for any list of any vehicle
public interface Callback<T extends Vehicle> {
void onListItemSelected(T selectedItem);
}
//common code for list of any vehicle
public VehicleListFragment() {
}
}
Bus, Truck, Boat, Bike, whatever list fragment:
public class BusListFragment
extends VehicleListFragment<Bus> {
//code specific for list of bus
public BusListFragment() {
}
}
Vehicle details fragment:
public abstract class VehicleDetailsFragment<T extends Vehicle, C extends VehicleDetailsFragment.Callback<T>>
extends CallbackFragment<C> {
//common methods of callback for any vehicle
public interface Callback<T> {
void onVehicleEdited(T editeItem);
}
//common code for any vehicle
public VehicleDetailsFragment() {
}
}
Bus, Truck, Boat, Bike, whatever details fragment:
public class BusDetailsFragment
extends VehicleDetailsFragment<Bus, BusDetailsFragment.Callback> {
//specific for Bus methods
public interface Callback
extends VehicleDetailsFragment.Callback<Bus> {
void onSomethingSpecificForBusHappened(Bus bus);
}
//code specific for Bus
public BusDetailsFragment() {
}
}
I've tried to add an abstract method for CallbackFragment to get callback class:
public abstract class CallbackFragment<C> extends Fragment {
...
#NonNull
protected abstract Class<C> getCallbackClass();
#Override
public void onAttach(Context context) {
super.onAttach(context);
...
//now checking instanceof like this
if(!getCallbackClass().isAssignableFrom(context.getClass())){
throw new RuntimeException(context.toString() + " must implement Callbacks");
}
}
}
With BusDetailsFragment everything looks OK:
public class BusDetailsFragment
extends VehicleDetailsFragment<Bus, BusDetailsFragment.Callback> {
#NonNull
#Override
protected Class<Callback> getCallbackClass() {
return Callback.class;
}
...
}
But not with BusListFragment:
public class BusListFragment
extends VehicleListFragment<Bus> {
#NonNull
#Override
protected Class<Callback<Bus>> getCallbackClass() {
/**
* I'm not seeing any option here
*
* mCallback - is null yet. So, there is no way to use mCallback.getClass()
*
* Callback<Bus>.class - Cannot select from parameterized type
*/
//return mCallback.getClass();
//return Callback<Bus>.class;
}
...
}
Of course, I could create an own interface for every subclass of VehicleListFragment that extends VehicleListFragment.Callback (like in subclasses of VehicleDetailsFragment) but it will always look like this:
public interface Callback
extends VehicleListFragment.Callback<Bus> {
//nothing more here
}
This doesn't look like the best option for me. Maybe there is any other solution? Please share your thoughts. ANY help would be appreciated.
mCallback = (C) context; //this line not seems to throw any exception
this call will never throw an Exception. During Runtime, your C is replaced with Object(that's called Type-Erasure) - and everything is an Object. Therefore you can assign anything at this point.
To have the exception (or at least error-determination) at the point, where you need it, you can use:
public abstract class CallbackFragment<C> extends Fragment {
protected C mCallback;
protected Class<C> callbackClass;
public CallbackFragment(Class<C> clazz) {
this.callbackClass = clazz;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
//just in case
if(context == null)
throw new NullPointerException();
if (clazz.isAssignableFrom(context.getClass()){
mCallback = (C) context;
}else{
//oops
}
}
}
ofc. then your FragmentCreation would change from
CallbackFragment<Something> fragment = new CallbackFragment<Something>();
to
CallbackFragment<Something> fragment = new CallbackFragment<Something>(Something.class);
It's a little different, but allows you to keep track of the actual type at any time, bypassing the Type-Erasure.
ps.: For Inherited classes, you can do it more generic:
public abstract class CallbackFragment<C> extends Fragment {
protected Class<C> callbackClass;
public CallbackFragment() {
this.callbackClass = (Class<C>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];;
}
}
public class CallbackFragmentOfSomething extends <CallbackFragment<Something>>{
}
This only fails, if your actual class is not defined due to inheritance, but "on the fly":
CallbackFragment<Something> fragment = new CallbackFragment<Something>();
(Everything untested / no copy paste, but should be somewhat accurate)

Android injecting class return null

I'm using JobQueue library and i can successful inject some class such as GithubService to that for example:
public interface GithubService {
#GET("repositories")
Call<List<GithubRepo>> getAllRepositories();
}
now i'm trying to inject other class as RxBus
public interface Bus {
void register(#NonNull Object observer);
<T> CustomSubscriber<T> obtainSubscriber(#NonNull Class<T> eventClass, #NonNull Consumer<T> receiver);
<T> void registerSubscriber(#NonNull Object observer, #NonNull CustomSubscriber<T> subscriber);
void unregister(#NonNull Object observer);
void post(#NonNull Object event);
}
into job file, but i get null
job class:
public class GetLatestRepositories extends Job implements JobManagerInjectable {
#Inject
transient GithubService githubService;
#Inject
transient Bus eventBus;
private Call<List<GithubRepo>> repositoryCall;
public GetLatestRepositories() {
super(new Params(JobPriority.MID).requireNetwork().persist());
eventBus.register(this); /* IS NULL*/
}
#Override
public void onAdded() {
}
...
#Override
public void inject(ApplicationComponent component) {
component.inject(this);
}
}
ApplicationComponent
#ActivitiesScope
#Component(dependencies = GithubApplicationComponent.class)
public interface ApplicationComponent {
void inject(ActivityRegister activityRegister);
void inject(ActivityStartUpApplication activityStartUpApplication);
void inject(GetLatestRepositories getLatestRepositories);
}
GithubApplicationComponent
#AlachiqApplicationScope
#Component(
modules = {
UserInformationModule.class,
NetworkServiceModule.class,
PicassoModule.class,
JobManagerModule.class,
RxModule.class,
ActivityModule.class
}
)
public interface GithubApplicationComponent {
Picasso getPicasso();
GithubService getGithubService();
JobManager getJobManager();
Bus getBus();
}
RxModule
#Module
public class RxModule {
#Provides
#AlachiqApplicationScope
public Bus getBus() {
return new RxBus();
}
}
RxBux files
In RxModule instead of:
return new RxBus();
Do:
return BusProvider.getInstance();

Dagger 2 not injecting in interface type

I an trying to inject a field in an interface type which is implemented by a class.
Here is what i have done so far.
These are view interface:
public interface PostView extends View, ListView<Post>, EmptyView<String> {
}
public interface View {
public void showProgressIndicator();
public void hideProgressIndicator();
public void onSuccess();
public void onFailure();
public void onFailure(String message);
}
public interface ListView<E> {
public void onListItems(List<E> items,
int pageNum,
int pageSize,
boolean next);
}
public interface EmptyView<E> {
public void onEmpty(E e);
public void onEmpty(String message);
}
Components:
#Singleton
#Component(modules = ApiModule.class)
public interface ApiComponent {
Api provideApi();
}
#UserScope
#Component(dependencies = ApiComponent.class, modules = PostModule.class)
public interface PostComponent {
PostPresenter providePostPresenter();
void inject(NetworkTest networkTest);
}
Modules:
#Module
public class ApiModule {
private static final Logger logger = Logger.getLogger(ApiModule.class.getSimpleName());
private final String baseUrl;
public ApiModule(String baseUrl) {
this.baseUrl = baseUrl;
}
#Provides
#Singleton
boolean provideIsLoggerEnabled() {
logger.info("proviedIsLoggerEnabled()");
return true;
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient(boolean logEnabled) {
logger.info(" provideOkHttpClient(logEnabled)");
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
Interceptor requestInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(chain.request());
}
};
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.addNetworkInterceptor(interceptor);
return builder.build();
}
#Provides
#Singleton
Api provideApi(OkHttpClient okHttpClient) {
logger.info("provideApi");
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit.create(Api.class);
}
}
#Module
public class PostModule {
private static final Logger logger = Logger.getLogger(PostModule.class.getSimpleName());
private final PostView postView;
public PostModule(PostView postView) {
this.postView = postView;
}
#Provides
#UserScope
PostService providePostService(Api api) {
logger.info("Provider post with api now");
return new PostService(api);
}
#Provides
#UserScope
PostPresenter providePostPresenter(PostService service) {
logger.info("Providing presenter with service now");
return new PostPresenter(postView, service);
}
}
Presenter:
public class PostPresenter extends AbstractPresenter {
private static final Logger logger = Logger.getLogger(PostPresenter.class.getSimpleName());
private PostView postView;
private PostService postService;
public PostPresenter(PostView postView, PostService postService) {
this.postView = postView;
this.postService = postService;
}
#Override
protected View getView() {
logger.info("Getting view");
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
public void getPosts() {
logger.info("Getting posts ");
Call<List<Post>> posts = this.postService.getPosts();
postView.showProgressIndicator();
posts.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> rspns) {
postView.onListItems(rspns.body(), 1, 25, true);
postView.hideProgressIndicator();
postView.onSuccess();
}
#Override
public void onFailure(Call<List<Post>> call, Throwable thrwbl) {
onApiCallError(thrwbl);
postView.hideProgressIndicator();
}
});
}
}
public abstract class AbstractPresenter {
private static final Logger logger = Logger.getLogger(AbstractPresenter.class.getSimpleName());
protected abstract View getView();
/*
* General indication whether api call stated or not.
*/
protected void onApiCallStart() {
logger.info("Api call started");
View v = getView();
if (v != null) {
v.showProgressIndicator();
}
}
protected void onApiCallEnd() {
logger.info("Api call finished");
View v = getView();
if (v != null) {
v.hideProgressIndicator();
}
}
/*
* General error handling
*/
protected void onApiCallError(Throwable e) {
logger.info("Api call terminated with error");
View v = getView();
if (v != null && e != null) {
v.onFailure(e.getMessage());
}
}
}
NetworkTest:
public class NetworkTest implements PostView {
private static final Logger logger = Logger.getLogger(NetworkTest.class.getSimpleName());
private PostComponent component;
#Inject
PostPresenter presenter;
public NetworkTest(ApiComponent apiComponent) {
component = DaggerPostComponent.builder()
.apiComponent(apiComponent)
.postModule(new PostModule(this))
.build();
}
public void init() {
component.inject(this);
}
void showPosts() {
if (presenter != null) {
logger.info("Hurray it worked");
presenter.getPosts();
} else {
logger.warning("Alas it failed");
}
}
#Override
public void showProgressIndicator() {
logger.info("Show progress indicator here");
}
#Override
public void hideProgressIndicator() {
logger.info("Hide progress indicator here");
}
#Override
public void onSuccess() {
logger.info("Api calls successfull");
System.exit(0);
}
#Override
public void onFailure() {
logger.warning("Api call failure");
System.exit(0);
}
#Override
public void onFailure(String message) {
logger.warning(message);
System.exit(0);
}
#Override
public void onListItems(List<Post> items, int pageNum, int pageSize, boolean next) {
logger.info("List received is: " + new Gson().toJson(items));
}
#Override
public void onEmpty(String e) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
public static void main(String[] args) {
ApiComponent apiComponent = DaggerApiComponent.builder()
.apiModule(new ApiModule("https://jsonplaceholder.typicode.com/"))
.build();
NetworkTest networkTest = new NetworkTest(apiComponent);
networkTest.init();
networkTest.showPosts();
}
}
My Problem is when i try to use
void inject(NetworkTest networkTest); //It works
void inject(PostView postView); //Doesn't work
I want that PostPresenter should get Injected in any class who is implementing PostView.
But when i do this #Inject field return null.
Does anyone have any clue about this.
NetworkTest has an #Inject field that Dagger can detect at compile time. PostView does not. Dagger 2 can perform injection on both NetworkTest and PostView, but because PostView has no #Inject-annotated methods, there's nothing for Dagger 2 to inject.
If you want to express that arbitrary implementors of PostView can be injected, you should add an #Inject-annotated initialize or injectPresenter method (et al); otherwise, just get/inject concrete types from Dagger so all of their dependencies can be injected at once.
As stated in the Dagger 2 user's guide (emphasis mine), "Dagger is a fully static, compile-time dependency injection framework for both Java and Android." Unlike with Guice or Spring, Dagger 2 performs no runtime reflection, so (for instance) a generated Component method inject(PostView) can only inject fields and methods defined on PostView or its supertypes, and not anything defined on a subtype.
In a general sense, I don't think it's reasonable for you to expect (or constrain) your PostView interface implementors to require the injection of a Presenter a certain way; if you want to make an explicit presenter-providing lifecycle method, you can do that on PostView without involving Dagger, and that way your classes can be more specific with their dependencies rather than mixing the necessary deps with the "unnecessary-but-included" deps you prescribe.

Categories

Resources