Follow the Android style rules and the Google Java style guide.
Follow these naming conventions in Android XML files:
-
Attributes (
attr
):shouldBeCamelCased
-
String, dimension and color names:
should_be_snake_cased
-
Themes and Styles:
ShouldBePascalCased
and should also be qualified in a similar manner to Java package names like<Type>.<Package>.<Name>...
. For instance:Theme.Collect.Light TextAppearance.Collect.H1.Purple Widget.Collect.Button Widget.Collect.Button.BigRed
Ensure that the added UI components are compatible with both light and dark themes. Follow the below points to get the color for coloring the UI components like text and icons instead of directly using color values (eg. #000000 or R.color.color_name).
UI Component | Java | Xml (layouts, drawables, vectors): |
---|---|---|
text color | themeUtils.getPrimaryTextColor() | ?primaryTextColor |
accent color | themeUtils.getAccentColor() | ?colorAccent |
icon color | themeUtils.getIconColor() | ?iconColor |
When creating or refactoring views, keep in mind our vision of an "ideal" view:
- Views should generally be as dumb and as stateless as possible
- Views shouldn't interact with other layers of your application (repositories, network etc). They should be able to take in and render data (via setters) and emit "meaningful" events via listeners.
- A view should have a single public
Listener
sub interface andsetListener
method. Methods on the interface should correspond to "meaningful" events. If the view is a button that could just beonClick
but if it's a volume slider this might be something likeonVolumeChanged
. - Views would ideally have just one setter for data but more complex views (often that have many subviews) that take in data at different times may have more - fewer setters is better as it's less changing state in the view.
- Views that render more than one kind of data (and that have more than one setter) might benefit from a
render
method that encapsulates all the logic around displaying the state of a view.
Always use string resources instead of literal strings. This ensures wording consistency across the project and also enables full translation of the app. Only make changes to the base res/values/strings.xml
English file and not to the other language files. The translated files are generated from Transifex where translations can be submitted by the community. Names of software packages or other untranslatable strings should be placed in res/values/untranslated.xml
.
Strings that represent very rare failure cases or that are meant more for ODK developers to use for troubleshooting rather than directly for users may be written as literal strings. This reduces the burden on translators and makes it easier for developers to troubleshoot edge cases without having to look up translations.
As much as possible to facilitate simpler, more modular and more testable components you should follow the Dependency Inversion principle in Collect Code. An example tutorial on this concept can be found here.
Because many Android components (Activity and Fragment for instance) don't allow us control over their constructors Collect uses Dagger to 'inject' dependencies. The configuration for Dagger can be found in AppDepdendencyComponent. For any normal objects it is probably best to avoid Dagger and use normal Java constructors.
While it's important to read the Dagger documentation we've provided some basic instructions on how to use Dagger within Collect below.
To declare a new dependency that objects can inject add a @Provider
method to the AppDepedencyModule
:
@Provider
public MyDependency providesMyDependency() {
return MyDependency();
}
You can also have Dagger return the same instance every time (i.e. a Singleton) by annotating the method with @Singleton
as well.
To inject a dependency into the Activity you first need to make Dagger aware it's injecting into that Activity by adding an inject
to the AppDependencyComponent
(if it's not already there):
void inject(MyActivity activity);
Then define a field with the @Inject
annotation in your Activity:
@Inject
MyDependency dependency;
To have Dagger inject the dependency you need to hook the injection into the Activity's onCreate
(as this is the first part of the lifecycle we have access to):
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerUtils.getComponent(this).inject(this);
}
For Fragment objects you should hook into the onAttach
lifecycle method instead:
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
DaggerUtils.getComponent(activity).inject(this);
}
To swap out depdendencies in a Robolectric test you can override the module the Application object uses to inject objects using provided helpers:
@Before
public void setup() {
MyDependency mocked = mock(MyDependency.class);
RobolectricHelpers.overrideAppDependencyModule(new AppDependencyModule() {
@Override
public MyDependency providesMyDependency() {
return mocked;
}
});
}
ODK Collect is released under the Apache 2.0 license. Please make sure that any code you include is an OSI-approved permissive license. Please note that if no license is specified for a piece of code or if it has an incompatible license such as GPL, using it puts the project at legal risk.
Sites with compatible licenses (including StackOverflow) will sometimes provide exactly the code snippet needed to solve a problem. You are encouraged to use such snippets in ODK Collect as long as you attribute them by including a direct link to the source. In addition to complying with the content license, this provides useful context for anyone reading the code.