Hi there, I'm attempting to modify ODK Collect so that I can print out Form Instances directly from a mobile device to a Wi-Fi printer.
I'm new to programming and so it's been a bit of a jump in the deep end but I have got somewhere at last. However I need some pointers!
I've managed to write an app that will take a hardcoded XML file in res/raw and use XSLT to transform it into HTML which Google's WebView then prints out successfully.
What I want to do now is replace the hardcoded XML file with the desired XML of the Form Instance that the user wants to print out.
But I'm stuck!
I know there are XML files for each Form Instance but I can't work out how they are linked back to the Form Instance that you see when using the UI.
When looking at a particular Form Instance in the UI would it be possible to retrieve a URI for its XML file?
If so then I could simply add this print button to my Form UI and have my print code fetch the relevant XML file and print it upon pressing the 'Print' button.
The hierarchy activity has access to the global FormController through Collect.getInstance().getFormController(). You can call getInstanceFile to get a reference to the XML document that represents the filled instance.
Ok, so I'm having a bit of trouble here, I keep getting an 'Unhandled exception: java.io.IOException´ with whichever method I attempt to convert the File to a String.
Not a very elegant solution as we still need to update the XSLT for each new Form Template that we want to print out, but it serves our needs at the minute!
If anyone is interested here is the main PrintFormInstanceActivity code:
package org.odk.collect.android.activities;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintJob;
import android.print.PrintManager;
import android.util.Log;
import android.view.Window;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.apache.commons.io.FileUtils;
import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class PrintFormInstanceActivity extends Activity {
public static final String TAG = "YOUR-TAG-NAME";
private WebView mWebView;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_PROGRESS);
WebView webview = new WebView(this);
setContentView(webview);
//Reading XSLT
String strXSLT = GetStyleSheet(R.raw.xsltfile);
//Reading XML
File InstanceXmlFile = Collect.getInstance().getFormController().getInstanceFile();
String strXML = null;
try {
strXML = FileUtils.readFileToString(InstanceXmlFile, "utf-8");
} catch (IOException e) {
e.printStackTrace();
}
//Transform ...
String html = StaticTransform(strXSLT, strXML);
//Loading the above transformed XSLT in to Webview...
webview.loadData(html, "text/html", null);
WebView webView = new WebView(this);
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
@Override
public void onPageFinished(WebView view, String url) {
Log.i(TAG, "page finished loading " + url);
createWebPrintJob(view);
mWebView = null;
}
});
webView.loadDataWithBaseURL(null, html, "text/HTML", "UTF-8", null);
mWebView = webView;
}
/** Google's WebView print code **/
private void createWebPrintJob(WebView webView) {
// Get a PrintManager instance
PrintManager printManager = (PrintManager) this
.getSystemService(Context.PRINT_SERVICE);
String jobName = getString(R.string.app_name) + " Document";
// Get a print adapter instance
PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(jobName);
// Set default page size to A4
PrintAttributes.Builder builder = new PrintAttributes.Builder();
builder.setMediaSize( PrintAttributes.MediaSize.ISO_A4);
// Create a print job with name and adapter instance
PrintJob printJob = printManager.print(jobName, printAdapter, builder.build());
// Save the job object for later status checking
// printJobs.add(printJob);
}
/**
* Transform XML and XSLT to HTML string
**/
public static String StaticTransform(String strXsl, String strXml) {
String html = "";
try {
InputStream ds = null;
ds = new ByteArrayInputStream(strXml.getBytes("UTF-8"));
Source xmlSource = new StreamSource(ds);
InputStream xs = new ByteArrayInputStream(strXsl.getBytes("UTF-8"));
Source xsltSource = new StreamSource(xs);
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer(xsltSource);
transformer.transform(xmlSource, result);
html = writer.toString();
ds.close();
xs.close();
xmlSource = null;
xsltSource = null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerFactoryConfigurationError e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return html;
}
/**
* Read XSLT file from res/raw...
**/
private String GetStyleSheet(int fileId) {
String strXsl = null;
InputStream raw = getResources().openRawResource(fileId);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int size = 0;
// Read the entire resource into a local byte buffer.
byte[] buffer = new byte[1024];
try {
while ((size = raw.read(buffer, 0, 1024)) >= 0) {
outputStream.write(buffer, 0, size);
}
raw.close();
strXsl = outputStream.toString();
Log.v("Log", "xsl ==> " + strXsl);
} catch (IOException e) {
e.printStackTrace();
}
return strXsl;
}
}
Was a bit hesitant about sharing the code as I know it's pretty horrendous, but it's my first shot at coding so I guess I don't even know how bad it really is
P.s. there maaay be some memory leak issues. I tried to narrow it down with the profiler but all I could see was something related to the GNSS stuff, so not sure if I did something or it's always like that?
Yes, creating a generic XSLT script that'll handle an arbitrary XForm is a bit trickier, since you have then to extract the question info from the XML form def (or pull it out of Collects's internal data structures). But this is still a great accomplishment, and may inspire others to also take up the challenge...
Hello. I have an urgent need to leave a copy of your answers to my respondents. This version allows you to obtain a PDF document of the questionnaire answered? In case it is so, how do I have this "version" of collect on my device? I have no knowledge of code. I'm from Paraguay. Best regards
Hi @marceloscarone, I have the version that allows PDF printing stored in a repository on my GitHub. I'm happy to share it but need to remove some personal data first.
However, unfortunately it will require some coding knowledge at your end so that you can tailor it it to suit your needs.
For each Form Template that you want to print the code needs to be altered to update the XSLT file so that it can format the printing correctly. This requires Android Studio to be installed and to clone the repository from GitHub so that you can then make changes to your own version of the app.
You'll need some understanding of XSLT and XML / HTML to be able to get your Form Templates to print out successfully. Also you'll need to be able to build the modified Android code into an installable apk file.
Depending on how urgent your need is and how much time you have available, it should be possible to learn how to do this - but it's definitely not as simple as just downloading the correct version of ODK Collect to be able to print to PDF I'm afraid