ClassNotFoundException for main activity - Android Studio 0.8.2 - android

Just updated to Android Studio 0.8.2 and I'm suddenly getting ClassNotFoundException on launch for the main activity when one of my apps is built with 0.8.2.
The app is in version control (Git) so I've checked out old builds and they are now exhibiting the same symptom upon execution and they were definitely working just fine when built with 0.8.1.
I've tried clean rebuilds, gradle syncing, manually deleting build/ and, .gradle/ dirs, closing & reopening Android Studio etc. - I've checked out various old builds of my project and tested the APK on several Android devices, the issue is 100% reproducible with both Debug and Release builds.
Strangely, although this is 100% reproducible for this particular project, I have another project which is building and executing just fine.
I've pulled apart the APK that is output from Android Studio 0.8.2 with apktool, and the class is definitely there.
$ pwd
/Users/ben/Development/go-android/Go/build/outputs/apk/Go-debug/smali/au/com/glassechidna/go/content
$ ls
AddTripFragment$1.smali CreateTripFragment$OnQueryTextListener$1.smali CreateTripFragment.smali MainActivity.smali
AddTripFragment.smali CreateTripFragment$OnQueryTextListener.smali GoActivity.smali TrainTimeFragment$1.smali
CreateTripActivity.smali CreateTripFragment$OnSuggestionListener.smali GoApplication.smali TrainTimeFragment.smali
CreateTripFragment$1.smali CreateTripFragment$StopCursor.smali MainActivity$PagerAdapter.smali TripFragment.smali
Stacktrace: https://gist.github.com/Benjamin-Dobell/a77bd8fffb03ddb37ff1
Manifest: https://gist.github.com/Benjamin-Dobell/ca7ee54d7562a0feab0c
Go/build.gradle[1]: https://gist.github.com/Benjamin-Dobell/f9a3a39d6f141e412dac
build.gradle[1]: https://gist.github.com/Benjamin-Dobell/2db744dd89e487325b65
MainActivity.java[2]: https://gist.github.com/Benjamin-Dobell/11a6335f56b6ababeeb4
Any ideas?
[1] Taken from one of the oldest commits, as it has less dependencies etc. cluttering stuff up. However, the issue is still 100% reproducible for this commit.
[2] Just the declaration.

Cleaning from CLI seems to have resolved the issue.
Props to #yogurtearl for pointing me in the right direction.
$ ./gradlew clean
Relying on packaging to define the extension of the main artifact has been deprecated and is scheduled to be removed in Gradle 2.0
:Go:clean
BUILD SUCCESSFUL
Total time: 3.906 secs
$ ./gradlew assembleDebug
Relying on packaging to define the extension of the main artifact has been deprecated and is scheduled to be removed in Gradle 2.0
:Go:preBuild
:Go:compileDebugNdk
:Go:preDebugBuild
:Go:checkDebugManifest
:Go:preReleaseBuild
:Go:prepareComAndroidSupportSupportV42000Library UP-TO-DATE
:Go:prepareDebugDependencies
:Go:compileDebugAidl
:Go:compileDebugRenderscript
:Go:generateDebugBuildConfig
:Go:generateDebugAssets UP-TO-DATE
:Go:mergeDebugAssets
:Go:generateDebugResValues UP-TO-DATE
:Go:generateDebugResources
:Go:mergeDebugResources
:Go:processDebugManifest
:Go:processDebugResources
:Go:generateDebugSources
:Go:compileDebugJava
:Go:preDexDebug
:Go:dexDebug
:Go:processDebugJavaRes UP-TO-DATE
:Go:validateDebugSigning
:Go:packageDebug
:Go:zipalignDebug
:Go:assembleDebug
BUILD SUCCESSFUL
Total time: 10.64 secs
$ adb uninstall au.com.glassechidna.go.debug
Success
$ adb install Go/build/outputs/apk/Go-debug.apk
8553 KB/s (323343 bytes in 0.036s)
pkg: /data/local/tmp/Go-debug.apk
Success
Update
Since running the gradlew commands above I can now clean and build just fine in Android Studio. Consequently, it would seem that Android Studio wasn't cleaning properly and that manually deleting build/ and .gradle/ directories wasn't sufficient either.
Now, I just need to understand why this worked and why cleaning and building in Android Studio didn't...
If you know about any additional state gradle/gradlew stores elsewhere that could be the culprit, I'd love to hear about it via a comment.
Update 2
This issue has kept occurring for me on my Mac. I've noticed that when I build in Android Studio the "preDexDebug" task generated several warnings as follows:
objc[49452]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/bin/java and /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined.
Comparing a good APK and a bad one I could see that some of the generated DEX'd classes vary quite significantly between the two APKs. Unfortunately I don't yet know a solution to the problem (I'm stuck building with the Gradle Wrapper from CLI).

Related

Delete unaligned apks using Gradle script in Android Studio 3.0?

After upgrading to Android Studio 3.0/build tools 3.0.0, a piece of my Gradle build script (which is used to delete unaligned apks after assembling) failed during build.
FYI I was using the Gradle scripts from this post.
Now it's always saying output.packageApplication.outputFile is an unknown property (Could not get unknown property 'outputFile' for task...).
I am wondering if there's an alternative way to get the unaligned file?
Any comment is appreciated, thanks in advance.
As of Gradle 2.2, there isn't an unaligned APK, so deleting it is unnecessary. (I just ran into this also - and noticed that they were missing.)
See the note here:
https://issuetracker.google.com/issues/37121010
So your code to remove them is unnecessary.
Also, to address the failure - as of Gradle 3.0 there are some features that are not working (like the variant outputFile). See the reference here on how to avoid that error:
https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#variant_api

Android gradlew.bat assembleRelease choose starting package

I have an Android Studio App that now has close to 100 "productFlavors" associated with it, of course all defined in the build.gradle. I would like to build them from command line since building them one at a time in the IDE would be a nightmare. My issue arises when I run into an error in the middle of building them using the command line gradlew.bat assembleRelease. Once I fix the error I would like to start building at the point of failure (maybe 50 flavors into the build) rather than at the beginning. Is there a way to do that with Gradle? With Ant I was forced to comment them out, I am hoping there is a better way with Gradle since the structure is much more complicated in the file.
UPDATE
I finally went with a solution that encompassed Gradle and Ant (yucky but it works). Basically I wanted to build on of my 100 White Label flavors, with a prefix of the version, and then copy it to the deliver APK without the version. To do this my ANT script looks like:
#!/bin/sh
DEPLOY_DIR=C:/AndroidBuilds/MyCompany.Build
VERSION="63"
NAME="WhiteLabel1"
DEPLOY_FOLDER="whitelabel1"
APK_NAME=$NAME$".apk"
gradlew assemble$NAME
cp $DEPLOY_DIR/$DEPLOY_FOLDER/$VERSION"-"$APK_NAME $DEPLOY_DIR/$DEPLOY_FOLDER/$APK_NAME
NAME="WhiteLabel2"
DEPLOY_FOLDER="whitelabel2"
APK_NAME=$NAME$".apk"
gradlew assemble$NAME
cp $DEPLOY_DIR/$DEPLOY_FOLDER/$VERSION"-"$APK_NAME $DEPLOY_DIR/$DEPLOY_FOLDER/$APK_NAME
This results in 2 APKs being generated, 63-WhiteLabel1.APK and 63-WhiteLabel2.APK, which are then copied to their respective deploy directories as WhiteLabel1.APK and WhiteLabel2.APK
I suppose if you are clever you could make this take parameters to run just the one you want, but Android Studio does a pretty decent job of that.
You can't specifically do that, but you can work around the issue and fix it with some script-magic.
If you have a build type called Release, and a flavor called Iron, you could run:
gradlew.bat assembleIronRelease
So you could write a script that would run through a loop of all flavors, and if it fails, it would tell you which one failed. You can then fix the error, and start the script again, telling it to start at X flavor (you would have to write the logic to tell it where to start though).

Failed to capture snapshot of output files for task

Error:Failed to capture snapshot of output files for task 'transformClassesWithDexForDebug' property 'streamOutputFolder' during up-to-date check.
Failed to create MD5 hash for file 'C:\Users\GauravZone\AndroidStudioProjects\ZopporoStore\app\build\intermediates\transforms\dex\debug\folders\1000\10000\instant-run_83dbc65f4e3a4cd278807c089695c6b352698791\classes.dex'.
I am getting these errors don't know why, all application working fine earlier but suddenly getting this error in my all applications when try to run projects
Disable your antivirus, and it will work.
This is a Gradle issue and you can find it here.
So as of now just disable Instant Run.
It worked for me.
In android studio,
Open the Settings or Preferences dialog: On Windows or Linux, select File >
Settings from the menu bar. On Mac OSX, select Android Studio > Preferences from the menu bar.
Navigate to Build, Execution, Deployment > Instant Run.
Uncheck The box next to Restart activity on code changes.
I Solved this issue in my react-native project by removing all npm cache
1.npm cache clean --force
2.react-native run-android
Set multiDexEnabled true in your buildType release class in your build.gradle file
Error:Failed to capture snapshot of output files for task 'transformClassesWithDexForDebug' property 'streamOutputFolder' during up-to-date check ::
Solution: Just rebuild the project and then sync the project with Gradle files, as mention below.
1. Click on Build -> Rebuild Project
2. Click on Tools -> Android -> Sync Project with Gradle Files
I was also getting the same error.
But when i disabled Instant Run, Gradle started building.
But i agree with those who think this is a Gradle issue.
And until it gets resolve, disable Instant Run.
follows steps: instant run(file->setting->build,execution,deployment->uncheck instant run) then compile it work properly.
I've run into this issue after updating Android Plugin for Gradle to version 2.3.1. Rollback to version 2.3.0 helped for me, try setting
classpath 'com.android.tools.build:gradle:2.3.0' in your top level gradle.build file.
For me i had this error when i use my device as emulator . I did like that and it works :
1.adb reverse tcp:8081 tcp:8081
2.react-native start --reset-cache
3.react-native run-android
I faced the same issue and by disabling the QuickHeal Antivirus's virus protection, it worked for me fine.
Problem is gradle version. Update the gradle version to latest to resolve this kind of issues.
I have tried 'com.android.tools.build:gradle:3.3.2' is working for me. previously it was '3.1.0'
This issue comes when there are more than one process which is doing something with a file in your disk (whether it's your Jenkins job's workspace or Gradle's cache path or Gradle build folder that it creates) where these processes are trying to do something with a file and holds a lock.
If you have anti-virus, you may see this. Un-installing may resolve the issue but then you don't have anti-virus (it's better if you can set something in anti-virus to scan files periodically).
In my case, this issue only comes when I have a Jenkinsfile pipeline, where I'm running Gradle task for 3-5 projects in parallel (let's say Gradle is running an rpm task or build task) and when I'm running concurrent runs of this pipeline.
As you can see my pipeline code (Jenkinsfile Pipeline DSL: How to Show Multi-Columns in Jobs dashboard GUI - For all Dynamically created stages - When within PIPELINE section) is creating dynamic stage(s) and when I call it within pipeline section, I use parallel to run N no. of projects (that I can define in a hash/array) gradle tasks in parallel.
What I noticed is, all these runs (running in parallel) is running using my user-id (or some service account) and they are all using same Gradle version (4.3.1 in my case) and thus, they are using the same cache.
Running them in parallel alone --OR-- more realistic case would be running multiple pipeline runs of this pipeline job (running 5 project's gradle task in parallel) would probably land into this LOCK file condition.
You can probably solve this by setting a unique GRADLE_USER_HOME="~${WORKSPACE}/.cache"
(i.e. use Jenkins job's workspace which is dynamically created and is unique) or even better by setting Gradle user home as:
dt_stamp=`date +%s`; ## set and pass this dt_stamp variable for Gradle user home before calling Gradle.
GRADLE_USER_HOME="~${WORKSPACE}/${dt}/.cache"`
This way, at least in Gradle's cache, you won't see any issues due to lock files (NOTE: You'll not be able to efficiently use Gradle's cache concept of being lazy (or not fetching artifacts/library dependencies from a binary repository like Artifactory each time) while using Gradle among many projects if you set GRADLE_USER_HOME to a dynamic folder value as it'd be like running gradle after clearing it's cache each time (for some it's OK if your build doesn't take much time and it's more cleaner way to build as well)).
When I tried to run just one pipeline run (which runs all 5 project's gradle task in paralle), the pipeline succeeds all the time.
Running multiple instances of the pipeline job (running 5 parallel builds in each run) lands into this file LOCK issue.
Error mesg when I ran many concurrent pipeline runs (each pipeline run - runs Gradle tasks in parallel on 5 different projects).
FAILURE: Build failed with an exception.
* What went wrong:
Failed to capture snapshot of output files for task ':rpm' property 'archivePath' during up-to-date check.
> Timeout waiting to lock file hash cache (/view/user123456_Team_Tools/vobs/space/test/folder1/Project1of5/.gradle/4.3.1/fileHashes). It is currently in use by another Gradle instance.
Owner PID: 29003
Our PID: 903
Owner Operation:
Our operation:
Lock file: /view/user123456_Team_Tools/vobs/space/test/folder1/Project1of5/.gradle/4.3.1/fileHashes/fileHashes.lock
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
-- OR --
You may also get this kind of error where a process is not able to delete a directory (while Gradle is in progress as the target folder/file may be in use by some other process)
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':clean'.
> Unable to delete directory: /view/user123456_Team_Tools/vobs/space/test/folder1/project2of5/build/classes/java/main/org/syntax/jedit
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
* Get more help at https://help.gradle.org
If you are really lucky (like me), then you'll probably hit this error as well:
Jenkins - java.lang.IllegalArgumentException: Last unit does not have enough valid bits &
Gradle error: Task 'null' not found in root project
See here for more details: Jenkins - java.lang.IllegalArgumentException: Last unit does not have enough valid bits & Gradle error: Task 'null' not found in root project
Clearing the cache does fix it. But only once, if you stop the project and re run it, you ll end up with same error again.
Doing following fixes it for me,
cd android
./gradlew clean
react-native start --reset-cache
npm clean cache --force
Next, open two different terminals,
Run react-native start
in one terminal and in second,
Run react-native run-android
Hope it'll work for you as well.

Error executing aapt: Return code 132 problems

Since updating my Android SDK about a month ago I've had endless problems leaving me with a non-functional development environment.
Initially the problem was with the v7-appcompat support library which threw up about 100 errors relating to various resources. It was fairly obvious they were resources relating to Android v5 and a quick search on Stack Overflow meant I realised the v7-appcompat library was targeting API 19. Changing the relevant settings to target API 21 fixed the resource errors.
However, the problem I've had since then is any attempt to build the library project quickly finishes with...
Error executing aapt: Return code 132
...and the library project isn't built correctly.
My original setup was with Eclipse Kepler but then I tried Android Studio v1.0.1 (same aapt error code) and today I installed Eclipse Luna to a separate directory from Kepler AND a fresh installation of ADT and SDK. Each version of Eclipse also have their own workspaces.
So basically I have two versions of Eclipse (each with their own ADT / SDK setups and workspaces) and Android Studio with its own directory structure and all three exhibit the same problem.
The question is, how do I go about diagnosing this issue? The common denominator is obviously an aapt issue but I have no idea how to fix it.
I'm running on a Debian Wheezy (32-bit) setup and I've got Java 1.7 installed correctly (as recommended).
There seems to be an issue with aapt on some 32 bit systems. It throws an error 132 on app:processDebugResources which is when aapt is 'crunching' png files ready for inclusion in the apk package. You can see more details at https://code.google.com/p/android/issues/detail?id=75110
Some people have reported that 'fixing' the offending png files (e.g. by opening in Gimp and saving) sorts the problem. When they are 'built in' to appcompat it's much harder to do of course.
Before getting into detail can you just say if you are indeed running on a 32 bit processor, and if so which one? use lscpu to find out.
OK, if anyone is interested, here is a work around so you can use your old laptop to build apps with Android Studio. Basically you strip all the png files in the appcompat-v7 library and then do a build using two versions of aapt. Yuk.
Find the appcompat-v7.aar which will be somewhere like ~/Android/Sdk/extras/android/m2repository/com/android/support/appcompat-v7/22.0.0
Extract it (it's a zip file really) to a temporary directory
Strip all the png files: find . -name "*.png" -exec mogrify -strip {} \;
Pack the files back up again: pushd appcompat-v7-22.0.0; zip -r ../appcompat-v7-22.0.0.aar; popd
Move the new aar file to replace the original (that may be enough to get a good build but it wasn't for me)
Now find an old version of aapt (I went to https://dl-ssl.google.com/android/repository/build-tools_r17-linux.zip)
Copy the version of aapt from the zip you've just picked up into the Android/Sdk/build-tools directory as aapt-17 or whatever
Rename the original aapt to aapt-0
Alternate between the two versions of aapt (e.g. ln -sf aapt-17 aapt) and you should find you can get a good build. One version will fail in one place, the other in another, getting you past all the bumps.
I know it stinks but I was desperate!!
Here's what I did, which is enough to get a successful build. Inspired by peterthevicar's answer.
downgrade sdk to 19.1.0, you can install it from studio and then change buildToolsVersion in build.gradle
rename aapt to aapt.bin and put a shell script in its place that intercepts just the png crunch (args: s -i infile -o outfile) and does a simple cp instead. would paste it here but SO's syntax highlighting suuuucks

Titanium Mobile: Cannot build android modules in 3.3.0

Update: To clarify, I can build the process successfully. The question is about installing the example project directly on a device (though I'm thinking emulator won't work either).
I developed a module about two years ago that allowed bluetooth connectivity on Android. I believe the latest version of the Titanium SDK at the time was around 2.0. I believe the specifics of the module and the example app are irrelevant to this question. I will ask my question first, and then try and explain the steps that I have gone through to lead me to the actual question.
My question is: is something in my environment misconfigured, or is this a bug in the Titanium SDK?
If it is the latter, I am very surprised that I can't find any information about it, since it prevents android modules from installing with ant in the latest SDK.
Information about my build environment:
Mac OS 10.9.5
Titanium Studio 3.3.0.GA
Ti CLI 3.3.0
Android 19
java -version: java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-462-11M4609)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-462, mixed mode)
I am trying to build it now for the latest SDK, 3.3.0 at the time of this writing. I found that when I tried to build (install) with ant, it resulted in the following output:
[exec] [ERROR] /var/folders/x7/h7lgzf1n4nx7fcwr26fdrzxr0000gn/T/mTUYC62ti/testmodule/build/android/res/values-v14/themes_base.xml:133: error: Error: No resource found that matches the given name: attr 'android:actionBarWidgetTheme'.
[exec] [ERROR] /var/folders/x7/h7lgzf1n4nx7fcwr26fdrzxr0000gn/T/mTUYC62ti/testmodule/build/android/res/values-v14/themes_base.xml:148: error: Error retrieving parent for item: No resource found that matches the given name 'android:Theme.Holo.Light.DialogWhenLarge'.
[exec] [ERROR] /var/folders/x7/h7lgzf1n4nx7fcwr26fdrzxr0000gn/T/mTUYC62ti/testmodule/build/android/res/values-v14/themes_base.xml:159: error: Error: No resource found that matches the given name: attr 'android:actionBarWidgetTheme'.
[exec] [ERROR] Error generating R.java from manifest
[exec] [ERROR] Build Failed.
BUILD SUCCESSFUL
Total time: 20 seconds
Not only that, but when creating a brand new mobile module project, and building straightaway with ant:
cd proj/android && ant install
The errors are the same. I should also point out that I reformatted not too long ago, so I find it unlikely that I have borked my titanium installation already. Now, from here, I've gotten into the Titanium build scripts to try and figure out what is happening. First, from the Titanium SDK 3.3.0 release notes:
Due to the addition of the appcompat library, there are a number of behavior changes to the application:
...
The target SDK must be set to API level 14 (Android 4.0.x) or higher, or you must have API level 14 or higher installed.
Now in the build output for my project:
[exec] [TRACE] Writing out AndroidManifest.xml
[exec] [DEBUG] /path/to/my/androidsdk/build-tools/17.0.0/aapt package -m -J /var/folders/x7/h7lgzf1n4nx7fcwr26fdrzxr0000gn/T/mTUYC62ti/testmodule/build/android/gen -M /var/folders/x7/h7lgzf1n4nx7fcwr26fdrzxr0000gn/T/mTUYC62ti/testmodule/build/android/AndroidManifest.xml -S /var/folders/x7/h7lgzf1n4nx7fcwr26fdrzxr0000gn/T/mTUYC62ti/testmodule/build/android/res -I /path/to/my/androidsdk/platforms/android-10/android.jar
Note that it's using the tools for android-10 and not android-14+ which is strange considering the relevant bits of my timodule.xml and build.properties:
timodule.xml:
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
<tool-api-level>19</tool-api-level><!-- deprecated - see below -->
build.properties:
titanium.platform=/Users/trey/Library/Application Support/Titanium/mobilesdk/osx/3.3.0.GA/android
android.platform=/Users/trey/sdk/android-sdk-macosx/platforms/android-19
google.apis=/Users/trey/sdk/android-sdk-macosx/add-ons/addon-google_apis-google-19
android.ndk=/Users/trey/sdk/android-ndk-r9d
As it turns out, it seems to be only the deprecated element that matters, and not in timodule.xml at all. Build scripts are found at $titanium_sdk/$os/$version/android/
Observation: Setting MIN_API_LEVEL = 19 (or 14) instead of 10 in builder.py fixes the issue. So the build script is ignoring my configuration and using the default. This seems to happen in the init method of the builder class:
temp_tiapp = TiAppXML(self.project_tiappxml)
if temp_tiapp and temp_tiapp.android:
if 'tool-api-level' in temp_tiapp.android:
self.tool_api_level = int(temp_tiapp.android['tool-api-level'])
tool_api_level_explicit = True
This sets the tool_api_level that the build script uses, but in my case it never gets called because there is not tiapp.xml. So I made a tiapp.xml, and put it in my example project. It has the same configs as the timodule.xml. As part of the build process this gets copied along with the rest of the example project into a temp folder that will be used to build and run the project. self.project_tiappxml in the code above is the root of the project. And there is still no tiapp.xml there, because it was copied to the Resources directory with everything else, so tool_api_level always gets set to the default of 10. That is basically as far as I've gotten.
So, again my question: is my configuration wrong, or is this a bug. It seems to me that if it is a bug, then no one would be able to build android modules, best I can tell. So if it's my fault, what have I done to cause it, and how can I fix it.
Update: If you saw my last edit, that was wrong and I'm sorry - my selected SDK for ti was 3.2.3! Fixed that and it creates the new project fine. It also builds. However, installing the example project still results in the same error as above:
[exec] [ERROR] Error generating R.java from manifest
[exec] [ERROR] Build Failed.
I also added a line above the call to aapt, to clarify that these errors occur while trying to write AndroidManifest.xml for the example project.
Its good you are working with 3.3.0, because from this version Android module creation is relatively easy.by viewing your build.properties i think something is missing or you forgot to add completely here it should be something close to :
titanium.platform=/Users/xxxxxx/Library/Application Support/Titanium/mobilesdk/osx/3.3.0.GA/android
android.platform=/Users/xxxxxx/Documents/adt-bundle-mac/sdk/platforms/android-16
google.apis=/Users/xxxxxx/Documents/adt-bundle-mac/sdk/add-ons/addon-google_apis-google-16
android.ndk=/Users/xxxxxx/Documents/android-ndk
but what i would suggest is check the following things as they get missed often:
you should have android-ndk installed and in the path
you must have gperf installed with your system(for mac comes with xcode command line tools)
Followed by running the following command in your terminal:
ti create -p android -t module -d <WORKSPACE_DIR> -n <MODULE_NAME> -u <MODULE_URL> --id <MODULE_ID>
## Sample Command
ti create -p android -t module -d ~/Documents/Sample_Workspace/ -n calc -u http:// --id org.appcelerator.calc
If everything is alright you will have a module in your Sample_Workspace folder with name calcand the given id.
you can work on it directly by importing it to your eclipse. and once done can build it using ant over the directory.
Hope it helps. Check the module creation guide.

Categories

Resources