How to create pdf and review in flutter - pdf

I'm trying to create pdf and review it.
I applied pdf plugin for creating the pdf , path_provider plugin for save the pdf to the device's storage and
flutter_full_pdf_viewer plugin for view the pdf.
I have followed create-a-pdf-in-flutter.
But getting errors in the code if I try to import with import 'package:pdf/widgets.dart'; , material element isn't working import 'package:flutter/material.dart'; .
What am I doing wrong?
Code:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdfdemo/pages/pdf_viewer.dart';
//import 'package:pdf/widgets.dart'
Variable:
final pdf = Document();
Creating pdf file page:
return Scaffold(
appBar: AppBar(title: Text("PDF CREATE"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () => savePdfFile(),
)
],),
body: pdf.addPage(Page(
pageFormat: PdfPageFormat.a4,
build: (BuildContext context) {
return Center(
child: Text("Hello Flutter"),
);
})),
);
Saving pdf file to the device's location:
savePdfFile()async{
final dir = await getExternalStorageDirectory();
print("Directoryyyyyyyyy:${dir.path}");
final String path = "${dir.path}/example.pdf";
final file = File(path);
await file.writeAsBytes(pdf.save());
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => PgfViewerPage(path: path))
);
}

The Problem in your code is that you are using the material library and the PDF library at the same time. The Widgets that are provided by the PDF plugin dont work in the regular Scaffold from flutter. You build your PDF with them like they are showing in the example. To get the PDF file you need to generate it first and then pass it to the screen where you wanna display it.
Try it like this, it worked for me
Future<File> createPDF(){
final Document pdf = Document();
pdf.addPage(
//Your PDF design here with the widget system of the plugin
MultiPage(
pageFormat:
PdfPageFormat.letter.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),
crossAxisAlignment: CrossAxisAlignment.start,
theme: Theme(
tableHeader: TextStyle(fontSize: 8.0),
tableCell: TextStyle(fontSize: 8.0),
),
header: (Context context) {
if (context.pageNumber == 1) {
return null;
}
return Container(
alignment: Alignment.centerRight,
margin: const EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
padding: const EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
decoration: const BoxDecoration(
border:
BoxBorder(bottom: true, width: 0.5, color: PdfColors.grey)),
child: Text('VCR',
style: Theme.of(context)
.defaultTextStyle
.copyWith(color: PdfColors.grey)));
},
);
output = await getTemporaryDirectory();
final file = File('${output.path}/example.pdf');
file.writeAsBytesSync(pdf.save());
return file;
}
After you created the PDF display it in a scaffold like this:
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
class PDFScreen extends StatelessWidget {
final String pathPDF;
PDFScreen({this.pathPDF});
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
the pathPDf you get from the first function if you call file.absolute.path
IMPORTANT: the function and the PDFScreen must be in separate files!! Where you implement the function for generating the PDF you MUST NOT import 'package:flutter/material.dart';
hope this helps

import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:intl/intl.dart' as intl;
import 'package:permission_handler/permission_handler.dart';
import 'package:screenshot/screenshot.dart';
import 'dart:typed_data';
import 'package:syncfusion_flutter_pdf/pdf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:open_file/open_file.dart';
// Will take screenshot of the widget and save in Unit8List and create pdf of //Unit8List
//paste this function where needed
openPDFofSS();
//Add controller
ScreenshotController screenshotController = ScreenshotController();
//define controller before in widget as
Screenshot(
controller: screenshotController,
child: Text("replace child with the widget you want to convert in pdf"),
),
// paste these function
Future<void> openPDFofSS() async {
await screenshotController.capture().then((Uint8List image) {
//Capture Done
setState(() {
pdfLoading = true;
//save screenshot into Uint8List image
_imageFile = image;
//convert Unit8List image into PDF
_convertImageToPDF();
saveImage(_imageFile);
});
}).catchError((onError) {
print(onError);
});
}
Future<void> _convertImageToPDF() async {
//Create the PDF document
PdfDocument document = PdfDocument();
//Add the page
PdfPage page = document.pages.add();
//Load the image.
final PdfImage image = PdfBitmap(_imageFile);
//draw image to the first page
page.graphics.drawImage(
image, Rect.fromLTWH(-20, -20, page.size.width - 50, page.size.height));
//Save the docuemnt
List<int> bytes = document.save();
//Dispose the document.
document.dispose();
//Get external storage directory
Directory directory = await getApplicationDocumentsDirectory();
//Get directory path
String path = directory.path;
//Create an empty file to write PDF data
File file = File('$path/Output.pdf');
//Write PDF data
await file.writeAsBytes(bytes, flush: true);
print(path);
//Open the PDF document in mobile
OpenFile.open('$path/Output.pdf');
setState(() {
pdfLoading = false;
});
}
Future<String> saveImage(Uint8List image) async {
await [Permission.storage].request();
final result = await ImageGallerySaver.saveImage(image, name: 'autosmart');
return result['filePath'];
}

Related

Flutter with SQFlite error on pushing to database

I convert the data from my class instance to a map then pass that map into a database which I think is set up correctly but am getting the following error:
Tried calling: insert(conflictAlgorithm: Instance of 'ConflictAlgorithm', data: _LinkedHashMap len:5, table: "sessionchunk")
Found: insert(String, Map<String, Object?>, {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm}) => Future<int>
heres my code:
globals.dart (bad file name, more of a database helper file)
library data_practice.globals;
import 'models/sessionModel.dart';
import "package:sqflite/sqflite.dart";
import "package:path/path.dart";
var database;
Future<Database> initDatabase() async {
return database = openDatabase(
// path to the database
join(await getDatabasesPath(), 'user_data.db'),
// database version
version: 1,
// on create
onCreate: (db, version) {
return db.execute(
"CREATE TABLE sessionchunk(id INTEGER PRIMARY KEY, worktime INTEGER, breaktime INTEGER, intention TEXT, progress TEXT)");
});
}
// create an instance of this to get access to the db
pushChunk(SessionChunk chunk) async {
//create instance of the mysql database, this might cause issues
//by having multiple instances??
var db = await database;
//takes table name,
await db.insert(
table: 'sessionchunk',
data: chunk.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
sessionModel.dart (this is the main model that I'm trying to store into the sessionchunk table)
import "package:path/path.dart";
import "package:sqflite/sqflite.dart";
import "package:data_practice/main.dart";
import "package:data_practice/globals.dart" as globals;
class SessionChunk {
int id = 0;
int workTime = 40;
int breakTime = 10;
String intention = "finish backend";
String progress = "done";
SessionChunk({required id, worktime, breakTime, intention, progress});
// helper method: converts chunk data into map for sqflite db
Map<String, dynamic> toMap() {
return {
"id": this.id,
"worktime": this.workTime,
"breaktime": this.breakTime,
"intention": this.intention,
"progress": this.progress,
};
}
// helper method: prints current values of refrenced session instance
#override
String toString() {
return 'SessionChunk{id: $id, workTime: $workTime, breakTime: $breakTime, intention: $intention, progress: $progress}';
}
}
then my main.dart file:
import 'package:flutter/material.dart';
import "package:path/path.dart";
import "package:sqflite/sqflite.dart";
import "./models/sessionModel.dart";
import 'globals.dart' as globals;
//TODO: left off at step 5 in on https://docs.flutter.dev/cookbook/persistence/sqlite
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Database db = await globals.initDatabase();
print(db);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Push to database, press below',
),
TextButton(
onPressed: () {
var chunk = SessionChunk(
id: 1,
);
globals.pushChunk(chunk);
},
child: Text("push"),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Any help with how doing this would be greatly appreciated, pretty new to all this so please explain like Im 5! Happy Friday :)

How to leave existing class attribute on image element - now it is being moved to a generated enclosing span

Background: Trying to use ckeditor5 as a replacement for my homegrown editor in a non-invasive way - meaning without changing my edited content or its class definitions. Would like to have WYSIWYG in the editor. Using django_ckeditor_5 as a base with my own ckeditor5 build that includes ckedito5-inspector and my extraPlugins and custom CSS. This works nicely.
Problem: When I load the following HTML into ClassicEditor (edited textarea.value):
<p>Text with inline image: <img class="someclass" src="/media/uploads/some.jpeg"></p>
in the editor view area, browser-inspection of the DOM shows:
...
<p>Text with an inline image:
<span class="image-inline ck-widget someclass ck-widget_with-resizer" contenteditable="false">
<img src="/media/uploads/some.jpeg">
<div class="ck ck-reset_all ck-widget__resizer ck-hidden">
<div ...></div></span></p>
...
Because the "someclass" class has been removed from and moved to the enclosing class attributes, my stylesheets are not able to size this image element as they would appear before editing.
If, within the ckeditor5 view, I edit the element using the browser inspector 'by hand' and add back class="someclass" to the image, ckeditor5 displays my page as I'd expect it with "someclass" and with the editing frame/tools also there. Switching to source-editing and back shows the class="someclass" on the and keeps it there after switching back to document editing mode.
(To get all this, I enabled the GeneralHtmlSupport plugin in the editor config with all allowed per instructions, and that seems to work fine.) I also added the following simple plugin:
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
#updateSchema() {
const schema = this.editor.model.schema;
schema.extend('imageInline', {
allowAttributes: ['class']
});
}
init() {
const editor = this.editor;
this.#updateSchema();
}
}
to extend the imageInline model hoping that would make the Image plugin keep this class attribute.
This is the part where I need some direction on how to proceed - what should be added/modified in the Image Plugin or in my Extend plugin to keep the class attribute with the element while editing - basically to fulfill the WYSIWYG desire?
The following version does not rely on GeneralHtmlSupport but creates an imageClassAttribute model element and uses that to convert only the image class attribute and place it on the imageInline model view widget element.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
#updateSchema() {
const schema = this.editor.model.schema;
schema.register( 'imageClassAttribute', {
isBlock: false,
isInline: false,
isObject: true,
isSelectable: false,
isContent: true,
allowWhere: 'imageInline',
});
schema.extend('imageInline', {
allowAttributes: ['imageClassAttribute' ]
});
}
init() {
const editor = this.editor;
this.#updateSchema();
this.#setupConversion();
}
#setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
conversion.for( 'upcast' )
.attributeToAttribute({
view: 'class',
model: 'imageClassAttribute'
});
conversion.for( 'dataDowncast' )
.attributeToAttribute({
model: 'imageClassAttribute',
view: 'class'
});
conversion.for ( 'editingDowncast' ).add( // Custom conversion helper
dispatcher =>
dispatcher.on( 'attribute:imageClassAttribute:imageInline', (evt, data, { writer, consumable, mapper }) => {
if ( !consumable.consume(data.item, evt.name) ) {
return;
}
const imageContainer = mapper.toViewElement(data.item);
const imageElement = imageContainer.getChild(0);
if ( data.attributeNewValue !== null ) {
writer.setAttribute('class', data.attributeNewValue, imageElement);
} else {
writer.removeAttribute('class', imageElement);
}
})
);
}
}
Well, Mr. Nose Tothegrind found two solutions after digging through ckeditor5 code, here's the first one. This extension Plugin restores all image attributes that are collected by GeneralHtmlSupport. It can be imported and added to a custom ckeditor5 build app.js file by adding config.extraPlugins = [ Extend ]; before the editor.create(...) statement.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import GeneralHtmlSupport from '#ckeditor/ckeditor5-html-support/src/generalhtmlsupport';
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
static get requires() {
return [ GeneralHtmlSupport ];
}
init() {
const editor = this.editor;
this.#setupConversion();
}
#setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
conversion.for ( 'editingDowncast' ).add( // Custom conversion helper
dispatcher =>
dispatcher.on( 'attribute:htmlAttributes:imageInline', (evt, data, { writer, mapper }) => {
const imageContainer = mapper.toViewElement(data.item);
const imageElement = imageContainer.getChild(0);
if ( data.attributeNewValue !== null ) {
const newValue = data.attributeNewValue;
if ( newValue.classes ) {
writer.setAttribute('class', newValue.classes.join(' '), imageElement);
}
if ( newValue.attributes ) {
for (const name of Object.keys(newValue.attributes)) {
writer.setAttribute( name, newValue.attributes[name], imageElement);
}
}
} else {
writer.removeAttribute('class', imageElement);
}
})
);
}a
}

How to build a login page in flutter using path_provider package for storing login in local file

I have created a save method and read method for storing credentials.
In the code below username and password are going to be saved in a file.
Code is also giving the directory path where it is going to be stored but it is not working properly while tapping on the login button. The code represents the login screen which appears when we open the app for the first time. The user account login page will open after filling the credentials. It will never ask to the user for logins till the user clear the data or uninstall the app. In this code username and password are the text filed, their is a checkbox in the login screen which will act as a remember me box. It will call the save method on click and now I want that checkbox will save credentials only after pressing the login button.
class LoginPageState extends State<LoginPage> {
TextEditingController username = new TextEditingController();
TextEditingController password = new TextEditingController();
bool checkValue = false;
String user ="username";
String pass="password";
#override
void initState() {
super.initState();
// call credentials method
readuser().then((String value) {
setState(() {
user="username";
user = value;
});
});
readpass().then((String value) {
setState(() {
pass="password";
pass = value;
});
});
}
bool checkingdata() {
if (user.length !=0 && pass.length != 0) {
return true;
} else {
return false;
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
elevation: 0.0,
backgroundColor: Colors.white12,
),
body: new SingleChildScrollView(
child: _body(),
scrollDirection: Axis.vertical,
),
);
}
Widget _body() {
return new Container(
padding: EdgeInsets.only(right: 20.0, left: 20.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Container(
margin: EdgeInsets.all(30.0),
child: new Image.asset(
"assets/images/flutter_icon.png",
height: 100.0,
),
),
new TextField(
onSubmitted: (_) {
setState(() {
user = username.text;``
});
},
controller: username,
decoration: InputDecoration(
hintText: "username",
hintStyle: new TextStyle(color: Colors.grey.withOpacity(0.3))),
),
new TextField(
controller: password,
obscureText: true,
onSubmitted: (_) {
setState(() {
pass = password.text;
});
},
decoration: InputDecoration(
hintText: "password",
hintStyle:
new TextStyle(color: Colors.grey.withOpacity(0.3)))),
new CheckboxListTile(
value: checkValue,
title: new Text("Remember me"),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (bool value) { checkingdata();
_saved(user, pass);
},
),
new Container(
decoration:
new BoxDecoration(border: Border.all(color: Colors.black)),
child: new ListTile(
title: new Text(
"Login",
textAlign: TextAlign.center,
),
onTap: _saved(user, pass),
),
)
],
),
);
}
//for username
_saved (String text,String text2) async {
print("asggd");
final userfile = await _localFile;
//print("asd");
// Write the file.
_savedpass(text2);
print("asded");
return userfile.writeAsString(text);
}
//for password
_savedpass(String text) async {
final passfile = await _localFile;
// Write the file.
return passfile.writeAsString(text);
}
Future<String> readuser() async {
try {
final userfile = await _localFile;
// Read the file.
String contents = await userfile.readAsString();
return contents;
} catch (e) {
// If encountering an error, return 0.
//return "dfd";
}
}
Future<String> readpass() async {
try {
final userpass = await _localFile;
// Read the file.
String contents = await userpass.readAsString();
return contents;
} catch (e) {
// If encountering an error, return 0.
//return;
}
}Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
var _localPath;
final path = await _localPath;
return File('$path/counter.txt');
}
}
Read function is used for the testing purposes. It will print the login credential on the home screen. I also want to add one more method in this code which is used to check the saved login credentials form the file so that next time when you reuse the app it will directly open the home screen.
Putting aside the fact that you want to store your credentials in plaintext in a local file, your problem is your _localFile getter. You define a local variable _localPath, so in the next line of code, all you're getting is null. Also, you probably want to create your file if it doesn't exist, simply by calling file.create().
you may not have the file counter.txt, so you need to create it first. I would advise you to use shared_preferences or hive to store your data.
For example with shared_preferences it will looks like in snippet below:
To retrieve data
final prefs = await SharedPreferences.getInstance();
final login = prefs.getString('login');
final password = prefs.getString('password');
// It's important to check it to the null
if (login != null && password != null) {
// Use it
}
To store new data
final prefs = await SharedPreferences.getInstance();
await prefs.setString('login', login);
await prefs.setString('password', password);
And you can remove from the preference as well:
final prefs = await SharedPreferences.getInstance();
await prefs.remove('login');
await prefs.remove('password');

Trying to make radio buttons with using Streambuilder and Bloc in Flutter. but don't work

I tried to make radiobuttons by using Streambuilder and Bloc.
so I made streamcontroler and when radiobuttons clicked,
I made streamcontrl.add(value) implemented, but Streambuilder don't listen
that stream. I tested onchanged value of radio. and
Please figure out what's wrong with it.
This is full code.
import 'package:flutter/material.dart';
import 'dart:async';
void main(){
runApp(new MaterialApp(
home: new MyApp(),
));
}
class bloc {
StreamController <int> ctrl = StreamController() ;
get blocvalue => ctrl.stream;
void setvalue (value ) {
ctrl.sink.add(value) ; }
}
class MyApp extends StatefulWidget {
#override
_State createState() => new _State();
}
class _State extends State<MyApp>{
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Name here'),
),
body: new Container(
padding: new EdgeInsets.all(15.0),
child: new Center(
child: new Column(
children: <Widget>[
StreamBuilder(
stream: bloc().blocvalue,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot <int> snapshot)
{
List<Widget> list = new List<Widget>();
for(int i = 0; i < 3; i++){
list.add(new RadioListTile(
value: i,
groupValue: snapshot.data,
onChanged: bloc().setvalue,
activeColor: Colors.green,
controlAffinity: ListTileControlAffinity.trailing,
title: new Text('Item: ${i}'),
dense: true,
// subtitle: new Text('sub title'),
));
}
return Column(children: list,); })
],
),
),
),
);
}
}
As mentioned by pksink in the comments, you're creating a new bloc inside build by calling setting it in StreamBuilder as bloc().blocValue. What you can do here is declare it as final myBloc = bloc(); outside of the bloc and set it on your StreamBuilder as myBloc.blocValue. With this, a new instance of bloc won't be created with every rebuild of Widget build.

Trying to load obj & mtl file with Three.js in React Native

Main objective : Load animated models exported from Maya into React Native app
Exported files : obj, mtl & png file
I have setup https://github.com/react-community/react-native-webgl in my React Native project and it is working properly.
Now, when I am trying to load the MTL file using the MTLLoader, I am getting following error:
Can't find variable: document
Apparently, the MTLLoader is calling TextureLoader which internally calls some load function which has 'document' reference. So what could be the solution to this ?
Here are the two files that I am using:
three.js
const THREE = require("three");
global.THREE = THREE;
if (!window.addEventListener)
window.addEventListener = () => { };
// require("three/examples/js/renderers/Projector");
require("three/examples/js/loaders/MTLLoader");
require("three/examples/js/loaders/OBJLoader");
export default THREE;
ThreeView.js
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import { WebGLView } from "react-native-webgl";
import THREE from "./three";
import { image } from "src/res/image";
export default class ThreeView extends Component {
requestId: *;
componentWillUnmount() {
cancelAnimationFrame(this.requestId);
}
onContextCreate = (gl: WebGLRenderingContext) => {
const rngl = gl.getExtension("RN");
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const renderer = new THREE.WebGLRenderer({
canvas: {
width,
height,
style: {},
addEventListener: () => { },
removeEventListener: () => { },
clientHeight: height
},
context: gl
});
renderer.setSize(width, height);
renderer.setClearColor(0xffffff, 1);
let camera, scene;
let cube;
function init() {
camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100);
camera.position.y = 150;
camera.position.z = 500;
scene = new THREE.Scene();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('female-croupier-2013-03-26.mtl', function (materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load('female-croupier-2013-03-26.obj', function (object) {
scene.add(object);
}, onLoading, onErrorLoading);
}, onLoading, onErrorLoading);
}
const onLoading = (xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
};
const onErrorLoading = (error) => {
console.log('An error happened', error);
};
const animate = () => {
this.requestId = requestAnimationFrame(animate);
renderer.render(scene, camera);
// cube.rotation.y += 0.05;
gl.flush();
rngl.endFrame();
};
init();
animate();
};
render() {
return (
<View style={styles.container}>
<WebGLView
style={styles.webglView}
onContextCreate={this.onContextCreate}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
webglView: {
width: 300,
height: 300
}
});
This error is as others have said caused by threejs trying to use features from a browser which react-native does not have.
I've gotten so far as to be able to load the textures (which is the stage you're getting the error from) by monkey patching the texture loader to use the loader in react-native-webgl. Add this in your init function (right near the top preferably).
//make sure you have defined renderer and rngl
/*
const renderer = new THREE.WebGLRenderer(...)
const rngl = gl.getExtension("RN");
*/
const loadTexture = async function(url, onLoad, onProgress, onError) {
let textureObject = new THREE.Texture();
console.log("loading",url,'with fancy texture loader');
let properties = renderer.properties.get(textureObject);
var texture = await rngl.loadTexture({yflip: false, image: url});
/*
rngl.loadTexture({ image: url })
.then(({ texture }) => {
*/
console.log("Texture [" + url + "] Loaded!")
texture.needsUpdate = true;
properties.__webglTexture = texture;
properties.__webglInit = true;
console.log(texture);
if (onLoad !== undefined) {
//console.warn('loaded tex', texture);
onLoad(textureObject);
}
//});
return textureObject;
}
THREE.TextureLoader.prototype.load = loadTexture;
This solves the problem of loading textures and I can see them load in Charles but they still don't render on a model so I'm stuck past that point. Technically a correct answer but you'll be stuck as soon as you've implemented it. I'm hoping you can comment back and tell me you've gotten further.
I had a similar setup and encountered same issue. My option was to switch to JSONLoader which doesn’t need document to render in react-native. So, I just loaded my model in Blender with a three-js addon, then exported it as json. Just check out this process of adding a three-js adon to Blender
https://www.youtube.com/watch?v=mqjwgTAGQRY
All the best
this might get you closer:
The GLTF format supports embedding texture images (as base64). If your asset pipeline allows it, you could convert to GLTF and then load into three/react-native.
I had to provide some "window" polyfills for "decodeUriComponent" and "atob" because GLTFLoader uses FileLoader to parse the base64:
I've successfully loaded embedded buffers, but you'll need more polyfills to load textures. TextureLoader uses ImageLoader, which uses document.createElementNS
You are using the MTLLoader which uses TextureLoader, and the TextureLoader uses the ImageLoader.
The imageloader uses the document.createElementNS() function.
what i did to solve this was to directly call the THREEjs TextureLoader:
let texture = new THREE.Texture(
url //URL = a base 64 JPEG string in this case
);
(for the use of Texture check the Texture documentation)
Then i used the Image class from React native (instead of the THREEjs Image, which requires the DOM to be constructed) to give that to the Texture as a property:
import { Image } from 'react-native';
var img = new Image(128, 128);
img.src = url;
texture.normal = img;
And then finally map the texture over the target material:
const mat = new THREE.MeshPhongMaterial();
mat.map = texture;
In the react native documentation it will explain how the react native Image element can be used, it supports base64 encoded JPEG.
Maybe there's a way for you to single out the part where it calls for the TextureLoader and replace that part with this answer. Let me know how it works out.
side note, i havent tried to display this yet in my webGLView, but in the logs it looked like normal threejs objects, it's worth the try
Use TextureLoader from expo-three
import { TextureLoader } from "expo-three";
export function loadTexture(resource) {
if (textureCache[resource]) {
return textureCache[resource].clone();
}
const texture = new TextureLoader().load(resource);
texture.magFilter = NearestFilter;
texture.minFilter = NearestFilter;
textureCache[resource] = texture;
return texture;
}
Source: https://github.com/EvanBacon/Expo-Crossy-Road/blob/master/src/Node/Generic.js